udp客户端发消息不卡住的关键是避免调用connect()导致send()阻塞,应直接使用sendto()并每次指定目标地址;需用htons()转换端口、检查返回值及错误码,并确保sockaddr_in结构体清零。

UDP客户端怎么发消息不卡住
UDP客户端发包后不阻塞,关键在 socket 创建后要避免调用 connect()(除非你明确需要绑定对端地址),否则 send() 可能因路由不可达而阻塞几秒。更稳妥的做法是直接用 sendto(),每次指定目标地址。
- 用
socket(AF_INET, SOCK_DGRAM, 0)创建非阻塞 socket 更安全,但即使默认阻塞模式,sendto()也几乎不等待 —— 它只负责把数据交给内核发送队列,不确认对方是否收到 - 务必检查
sendto()返回值:返回 -1 且errno == EAGAIN或EWOULDBLOCK表示发送缓冲区满(极少发生);errno == ENETUNREACH或EHOSTUNREACH表示网络层不可达,此时应记录错误而非重试 - 目标地址结构体
sockaddr_in中的sin_port必须用htons()转换,写成12345直接赋值会因字节序错乱导致连不到服务端
UDP服务端如何收包并避免丢数据
UDP本身无连接、无重传,服务端丢包主因是接收缓冲区溢出或未及时读取。Linux 默认 UDP 接收缓冲区仅 256KB 左右,突发大量小包时内核会直接丢弃。
- 启动前用
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize))扩大接收缓冲区(例如设为 1MB),注意实际生效值可能被系统限制(/proc/sys/net/core/rmem_max) -
recvfrom()必须循环调用直到返回 -1 且errno == EAGAIN,不能只调一次 —— 因为一个 recvfrom 只取一个 UDP 数据报,即使缓冲区里有多个包也得反复收 - 不要在
recvfrom()后立刻用inet_ntop()解析地址再打印,高频场景下字符串转换开销明显;调试时可先存sockaddr_in,需要时再解析
为什么客户端收不到服务端回包
最常见原因是服务端调用 sendto() 时用了错误的 client 地址 —— 比如把 recvfrom() 填充的 addr 结构体长度参数写错,或没保存 client 的 sockaddr_in 就直接复用栈变量。
-
recvfrom()的第 5 个参数必须传入指向socklen_t类型变量的指针(如&addrlen),且调用前要初始化为sizeof(addr);否则 Windows 下可能 crash,Linux 下可能地址截断 - 服务端回包必须用和收到包时**完全相同的**
sockaddr_in和addrlen,哪怕只改了sin_zero字段也可能导致目标端口错乱 - 防火墙或 NAT 设备可能拦截“非请求发起方向”的 UDP 包,测试时先关掉本地防火墙(
sudo ufw disable),确认逻辑正确后再加白名单
跨平台编译时要注意哪些头文件和链接项
Windows 和 Linux 的 socket API 差异集中在初始化和清理,头文件和链接方式稍不注意就编译不过。
- Windows 需包含
#include <winsock2.h></winsock2.h>和#include <ws2tcpip.h></ws2tcpip.h>,且必须在其他 socket 头文件之前;Linux 只需<sys></sys>、<netinet></netinet>、<arpa></arpa> - Windows 下首次使用 socket 前必须调用
WSAStartup(MAKEWORD(2,2), &wsaData),结束前调WSACleanup();Linux 无需对应操作 - 链接时 Windows 需加
-lws2_32,Linux 加-lsocket(部分发行版可省略);CMake 中可用find_package(Threads)+target_link_libraries(... ${CMAKE_DL_LIBS})兼容处理
UDP 的“简单”是假象:没有连接状态帮你兜底,每个 sendto/recvfrom 都得自己校验地址、长度、错误码。最容易被忽略的是 sockaddr_in 结构体中 sin_zero 字段未清零,导致后续地址比较失败或端口解析错误 —— 初始化时用 memset(&addr, 0, sizeof(addr)) 是铁律。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











