Socket编程实例(UDP)

根据前文手写一个简单的UDP通信实例

Socket编程讲解

IP 字符串 → 网络序数字

复制代码
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);

人类可读的点分十进制 IP 字符串 (如 "192.168.1.100")转换为网络字节序(大端)的二进制整数 ,用于 Socket 编程中配置 sockaddr_in 等地址结构体。

1. 核心优点

  • 跨平台:POSIX 标准,支持 Linux/BSD/macOS,替代老旧的 inet_addr
  • 无坑:能正确处理 255.255.255.255inet_addr 会误判为失败);
  • 支持 IPv6与IPV4:仅需将地址族改为 AF_INET6,适配性强;
  • 线程安全:无静态缓冲区,结果写入用户指定的内存。
参数 含义 注意事项
af 地址族:IPv4 填 AF_INET,IPv6 填 AF_INET6 必须与 src 的 IP 类型匹配
src 输入:待转换的 IP 字符串(如 "192.168.1.100" 格式错误会返回 0
dst 输出:存储网络序数字的缓冲区 IPv4 传 struct in_addr*,IPv6 传 struct in6_addr*

2. 使用例子(IPv4 核心场景)

复制代码
#include <stdio.h>
#include <arpa/inet.h>
#include <errno.h>

int main() {
    const char *ip_str = "192.168.1.100"; // 待转换的IP字符串
    struct in_addr ip_net;                // 存储网络序结果的结构体

    // 调用inet_pton转换
    int ret = inet_pton(AF_INET, ip_str, &ip_net);
    
    // 解析返回值
    if (ret == 1) {
        // 成功:打印网络序数字(十六进制更直观)
        printf("IP字符串 %s → 网络序数字:0x%x\n", ip_str, ip_net.s_addr);
        // 输出:IP字符串 192.168.1.100 → 网络序数字:0xc0a80164
    } else if (ret == 0) {
        printf("错误:IP字符串格式无效(%s)\n", ip_str);
    } else { // ret == -1
        perror("inet_pton 失败"); // 如af参数错误、内存错误等
        return -1;
    }
    return 0;
}

3. 返回值说明

返回值 含义 处理方式
1 转换成功 直接使用 dst 中的网络序数字
0 IP 字符串格式无效(如 "192.168.1.300" 检查 IP 字符串拼写
-1 函数调用失败(如 af 传错、dst 内存非法) perror() 查看具体错误(如 EAFNOSUPPORT 表示地址族不支持)

网络序数字 → IP 字符串

复制代码
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

网络字节序(大端)的二进制整数 (如 0xc0a80164)转换为人类可读的点分十进制 IP 字符串,用于打印、日志输出或展示 IP 地址。

1. 核心优点

  • 线程安全:需用户手动传入缓冲区,无静态缓冲区覆盖问题(对比老旧的 inet_ntoa);
  • 支持 IPv6:兼容 IPv4/IPv6,无需改逻辑;
  • 防越界:通过缓冲区大小参数控制,避免内存溢出;
  • 跨平台:POSIX 标准,替代线程不安全的 inet_ntoa
参数 含义 注意事项
af 地址族:IPv4 填 AF_INET,IPv6 填 AF_INET6 必须与 src 的 IP 类型匹配
src 输入:网络序数字的缓冲区(IPv4 传 struct in_addr* 必须是网络序(大端),无需转主机序
dst 输出:存储 IP 字符串的缓冲区 需提前分配内存
size 缓冲区大小:IPv4 用 INET_ADDRSTRLEN(固定 16 字节),IPv6 用 INET6_ADDRSTRLEN(46 字节) 过小会返回 NULL,触发 ENOSPC 错误

2. 使用例子

复制代码
#include <stdio.h>
#include <arpa/inet.h>
#include <errno.h>

int main() {
    // 待转换的网络序数字(对应 192.168.1.100)
    struct in_addr ip_net;
    ip_net.s_addr = 0xc0a80164;

    // 分配缓冲区(IPv4 固定16字节,用宏更安全)
    char ip_str[INET_ADDRSTRLEN];
    
    // 调用inet_ntop转换
    const char *ret = inet_ntop(AF_INET, &ip_net, ip_str, INET_ADDRSTRLEN);
    
    // 解析返回值
    if (ret != NULL) {
        printf("网络序数字 0x%x → IP字符串:%s\n", ip_net.s_addr, ip_str);
        // 输出:网络序数字 0xc0a80164 → IP字符串:192.168.1.100
    } else {
        perror("inet_ntop 失败"); // 如缓冲区过小、af参数错误
        return -1;
    }
    return 0;
}

3. 返回值说明

返回值 含义 处理方式
非 NULL 转换成功:返回 dst 缓冲区的地址(可直接打印) 直接使用 dst 中的 IP 字符串
NULL 转换失败 perror() 查看错误:① ENOSPC:缓冲区 size 过小;② EAFNOSUPPORTaf 参数错误;③ EINVALsrc 不是有效的网络序 IP

UDP发数据sento

复制代码
#include <sys/socket.h>
ssize_t sendto(
    int sockfd,          // ① UDP 的 socket 文件描述符(用 socket() 创建的)
    const void *buf,     // ② 要发送的数据缓冲区(比如存字符串的数组)
    size_t len,          // ③ 要发送的数据长度(字节数,比如 strlen("hello"))
    int flags,           // ④ 一般传 0(阻塞发送);非阻塞传 MSG_DONTWAIT
    const struct sockaddr *dest_addr, // ⑤ 目标地址:对方的 IP+端口(结构体)
    socklen_t addrlen    // ⑥ 目标地址结构体的长度(sizeof(struct sockaddr_in))
);

参数讲解

|-----------|----------------------------------------------------------------|
| sockfd | 你创建的 UDP "通信端口"(比如 int fd = socket(AF_INET, SOCK_DGRAM, 0);) |
| buf | 要发的数据存在这里(比如 char msg[] = "你好 UDP";) |
| len | 数据有多长(比如 sizeof(msg)strlen(msg)) |
| flags | 新手直接填 0 就行(阻塞模式,等数据发出去) |
| dest_addr | 告诉系统 "要发给谁":填对方的 IP 和端口(用 struct sockaddr_in 封装) |
| addrlen | 固定填 sizeof(struct sockaddr_in)(告诉系统地址结构体的大小) |

返回值讲解

|-----|-----------------------------------------------------------------------|
| >0 | 成功!返回实际发送的字节数(UDP 要么发完一个包,要么失败,不会只发一半) |
| -1 | 失败!常见原因:① errno=EAGAIN(非阻塞时发送缓冲区满);② errno=EBADF(sockfd 已关闭);③ 目标地址无效 |
| 0 | 成功发送0个字节(如下图) |


UDP接受数据recvfrom

复制代码
#include <sys/socket.h>
// 返回值:成功=实际接收的字节数;失败=-1(无0返回!)
ssize_t recvfrom(
    int sockfd,          // ① UDP 的 socket 文件描述符
    void *buf,           // ② 接收数据的缓冲区(存收到的内容)
    size_t len,          // ③ 缓冲区最大长度(避免数据装不下)
    int flags,           // ④ 一般传 0(阻塞接收);非阻塞传 MSG_DONTWAIT
    struct sockaddr *src_addr, // ⑤ 输出参数:存储"发送方"的 IP+端口
    socklen_t *addrlen   // ⑥ 输入输出参数:先填 sizeof(struct sockaddr_in),返回实际长度
);

参数讲解

|----------|---------------------------------------------|
| sockfd | 和 sendto 用同一个 UDP socketfd |
| buf | 收到的数据存在这里(比如 char buf[1024] = {0};) |
| len | 缓冲区能装多少字节(比如 sizeof(buf)-1,留 1 字节存字符串结束符) |
| flags | 新手填 0 就行(阻塞模式,等有数据才返回) |
| src_addr | 系统会把 "谁发的消息" 填到这里(比如知道对方的 IP 和端口) |

addrlen值-结果 参数:

  1. 调用前 你必须先给 src_addr 分配空间(struct sockaddr_in peer;

    复制代码
    socklen_t addrlen = sizeof(peer);   // 告诉内核:我准备的结构体有多大
  2. 调用时 内核把实际填写的字节数写回 addrlen(比如 sizeof(struct sockaddr_in))。

    复制代码
    ssize_t n = recvfrom(sockfd, buf, len, flags,
                         (struct sockaddr *)&peer, &addrlen);
  3. 调用后

    • peer 里拿到发送方 IP+端口

    • addrlen 变成已用长度,后续可原样传给 sendto 回包,或仅做日志。

"先给大小,后得大小"------这就是值-结果参数的用法。

返回值 TCP 调用改函数 UDP 调用该函数
>0 收到 N 字节数据 收到 N 字节数据
0 对端优雅关闭(FIN) 收到空数据报
-1 出错,查 errno 出错,查 errno

注意!!!写端关闭,不会导致UDP的读端返回0,如果是阻塞模式他会卡住。


错误码

错误码 触发场景 处理策略
EAGAIN / EWOULDBLOCK 非阻塞 socket 缓冲区空(读)或满(写) 立即返回 ,应用稍后再次 recv/send(轮询/epoll 等待就绪)
EINTR 信号中断了系统调用,没数据也没错 重新调用 同一函数(通常包在 do{...}while(...) 里)
其他 发生错误 应当打印日志然后直接退出

代码示例

server.cc

复制代码
//socket编程
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<iostream>
using namespace std;
//服务端
int main(){
    //ip 127.0.0.1   端口8080 开启地址重用
    int fd=socket(AF_INET,SOCK_DGRAM,0);//ip4 ,udp
    if(fd<0){
        cout<<"socket faild"<<endl;
    }
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(8080);
    inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
    int ret=bind(fd,(sockaddr*)&addr,sizeof addr);
    if(ret<0)
    cout<<"bind faild"<<endl;
    int i=0;

    while(true){
i++;
sleep(1);

        char buff[1024];
        sprintf(buff,"%s :%d","hello client",i);
    struct sockaddr_in addr1;
    addr1.sin_family=AF_INET;
    addr1.sin_port=htons(8081);
    inet_pton(AF_INET,"127.0.0.1",&addr1.sin_addr);
    socklen_t len=sizeof addr1;
   int n= sendto(fd,buff,sizeof buff,0,(sockaddr*)&addr1,len);
    cout<<"send over:n"<<n<<endl;
   size_t n=recvfrom(fd,buff,sizeof buff,0,(sockaddr*)&addr1,&len); 
   cout<<"client say:"<<buff<<endl;
}
}

client.cc

复制代码
//socket编程
#include<sys/socket.h>
#include<arpa/inet.h>
#include<iostream>
#include <unistd.h>
using namespace std;
//服务端
int main(){
    //ip 127.0.0.1   端口8080 开启地址重用
    int fd=socket(AF_INET,SOCK_DGRAM,0);//ip4 ,udp
    if(fd<0){
        cout<<"socket faild"<<endl;
    }
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(8081);
    inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
    int ret=bind(fd,(sockaddr*)&addr,sizeof addr);
    if(ret<0)
    cout<<"bind faild"<<endl;
    int i=0;

    while(true){
i++;
sleep(1);
        char buff[1024];
    struct sockaddr_in addr1;
    socklen_t len=sizeof addr1;
   size_t n=recvfrom(fd,buff,sizeof buff,0,(sockaddr*)&addr1,&len); 
   cout<<"server say:"<<buff<<endl;
    sprintf(buff,"%s :%d","hello server",i);
   cout<<"n:"<<n<<endl;
    sendto(fd,buff,sizeof buff,0,(sockaddr*)&addr1,len);
}
}

此份代码必须先启动客户端在启动服务端否则会卡死,为什么?

相关推荐
真上帝的左手2 小时前
7. 网络安全-等保
网络·安全·web安全
一颗青果2 小时前
Socket编程(TCP)
网络·网络协议·tcp/ip
tjjingpan3 小时前
HCIP-Datacom Core Technology V1.0_12流量过滤与转发路径控制
网络
qq. 28040339843 小时前
http 状态码
网络·网络协议·http
TG:@yunlaoda360 云老大3 小时前
华为云国际站代理商申请跨账号代维权限的流程复杂吗?
网络·数据库·华为云
小张的博客之旅3 小时前
“复兴杯”2025第五届大学生网络安全精英赛 (排位赛wp)
网络·安全·web安全
"YOUDIG"3 小时前
全能安全工具箱:智能密码生成、高强度文件加密与动态二维码生成的一站式平台
服务器·网络·安全
一颗青果3 小时前
序列化与反序列化
网络·网络协议·tcp/ip
安全渗透Hacker4 小时前
新一代特征扫描器afrog与经典引擎Xray深度解析
网络·安全·web安全·网络安全·自动化·系统安全·安全性测试