UDP通信必须调用socket(AF_INET, SOCK_DGRAM, 0)创建套接字,服务端需bind()绑定固定端口,客户端可省略;收发必须使用sendto()和recvfrom(),每次均需显式指定或获取地址信息,且单UDP数据报天然有消息边界但无可靠性保障。

UDP通信前必须调用socket()创建套接字,且类型必须是SOCK_DGRAM
UDP是无连接协议,不走connect()或listen()流程,但套接字本身仍需明确协议族和类型。常见错误是误用SOCK_STREAM(TCP)——这会导致sendto()失败并返回EINVAL。
正确写法:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
注意三点:
-
AF_INET表示IPv4;若需IPv6,改用AF_INET6,且后续sockaddr_in要换成sockaddr_in6 - 第三个参数填
0即可,内核会自动选IPPROTO_UDP;显式写IPPROTO_UDP也可,但非必需 - Windows下需先调用
WSAStartup(),Linux/macOS无需
sendto()和recvfrom()才是UDP收发核心,不是send()/recv()
UDP没有“已连接”状态,每次收发都必须显式指定目标地址或接收方地址。用send()会返回ENOTCONN错误;用recv()可能阻塞或读到错误数据(比如来自其他端口的包)。
立即学习“C++免费学习笔记(深入)”;
典型发送逻辑:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
struct sockaddr_in dest; dest.sin_family = AF_INET; dest.sin_port = htons(8080); inet_pton(AF_INET, "127.0.0.1", &dest.sin_addr); sendto(sockfd, buf, len, 0, (struct sockaddr*)&dest, sizeof(dest));
接收时务必传入sockaddr*和socklen_t*来获取来源信息:
- 哪怕你只关心数据,也得传一个
struct sockaddr_in变量和它的长度指针,否则recvfrom()可能写越界或返回EINVAL - 长度变量必须初始化,例如
socklen_t addrlen = sizeof(src);,不能只声明不赋值 - 如果不需要来源地址,可传
nullptr和nullptr,但这样就无法区分多个客户端了
绑定本地端口用bind(),但客户端可跳过,服务端必须做
服务端要监听特定端口(如53、67),必须bind();客户端通常不用绑定,系统会自动分配临时端口。强行给客户端bind()到固定端口,反而可能导致多实例启动失败(Address already in use)。
绑定示例(服务端):
struct sockaddr_in local; local.sin_family = AF_INET; local.sin_port = htons(9999); local.sin_addr.s_addr = INADDR_ANY; // 接收所有网卡的包 bind(sockfd, (struct sockaddr*)&local, sizeof(local));
关键点:
-
INADDR_ANY比写死inet_addr("127.0.0.1")更通用,避免只能收本机回环流量 - 若想只监听某张网卡,把
INADDR_ANY换成对应IP的inet_addr()结果 - Linux下未加
SO_REUSEADDR选项时,进程崩溃后端口可能处于TIME_WAIT状态,导致重启时报错;加选项可复用:
int opt = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
UDP没有内置分包/粘包概念,但应用层要自己处理消息边界
TCP靠流式传输+序列号隐含边界,UDP每个sendto()对应一个独立IP数据报,内核保证“整包到达或全丢”,所以天然有消息边界。但这不等于安全:
- IPv4单包上限约65507字节(65535 - 20IP头 - 8UDP头),超长数据会被IP层分片;任一分片丢失,整包就收不到
- 实际建议单包控制在1400字节以内(避开以太网MTU 1500减去IP/UDP头),降低分片概率
- 若需传输大块数据(如文件),必须在应用层切片、编号、重传——UDP本身不提供这些
- 不要依赖
recvfrom()一次读完全部数据:它最多读取缓冲区大小(默认几KB),且每次只返回一个UDP包的内容
缓冲区大小可通过setsockopt()调整,但治标不治本;真正健壮的UDP程序,得从协议设计上定义包头(含长度字段)、校验和、序列号。

















