udp双向通信需客户端bind()以分配固定接收端口,否则回包因无对应socket被内核丢弃;服务端必须原样使用recvfrom填充的地址结构体回包,且收发地址字段(如sin_family、sin_port字节序)须严格正确。

UDP 双向通信不靠“高性能”堆,而靠地址复用、字节序正确、收发匹配这三件事做对;盲目开多线程或用 epoll/kqueue 反而容易引入竞态和复杂度,多数场景单线程 recvfrom/sendto 就够用。
UDP 客户端为什么 bind() 后才能收到回包
不 bind() 的客户端,sendto() 能发出去,但内核不会为它分配固定接收端口——回包到达时找不到对应 socket,直接丢弃,且无任何错误提示。这是最常被忽略的“发得出去收不回来”原因。
- 客户端必须调用
bind(),哪怕传sin_port = 0让系统自动分配,之后用getsockname()获取实际端口 - 服务端回复时,必须原样使用
recvfrom()填充的sockaddr_in结构体(含正确sin_port和sin_addr),不能重新构造或二次调用htons() - Windows 下若跳过
WSAStartup(),bind()会失败且WSAGetLastError()返回WSANOTINITIALISED
服务端如何避免 recvfrom 阻塞导致吞吐下降
阻塞本身不是问题,问题是把阻塞当成“卡死”去优化——recvfrom() 阻塞只表示没数据来,CPU 是空闲的;真要压测或高并发,瓶颈在内存拷贝和系统调用开销,不在阻塞模型。
- 不要在循环里重复
bind(),失败时 errno 是EADDRINUSE,说明端口已被占,不是代码逻辑错 - 每次
recvfrom()前必须初始化socklen_t addr_len = sizeof(client_addr);若设为 0,Linux 返回 0 字节,Windows 可能崩溃 - 如需非阻塞,用
fcntl(sockfd, F_SETFL, O_NONBLOCK)(Linux)或ioctlsocket(sockfd, FIONBIO, &nonblocking)(Windows),但要处理EAGAIN/WSAEWOULDBLOCK
sendto / recvfrom 地址参数常见错位点
地址结构体填错一个字段,比如 sin_family 没设成 AF_INET,或 sin_port 忘了 htons(),发出去的包对方就解析不了——不是丢包,是根本收不到有效数据。
-
sendto()的目标地址中:sin_family必须为AF_INET,sin_port必须用htons(port),sin_addr.s_addr必须用inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr)或inet_addr() -
recvfrom()的addrlen参数是输入输出参数:调用前设为sizeof(sockaddr_in),返回后会被改写为实际写入长度,不可复用旧值 - 传给
sendto()的地址长度必须是sizeof(sockaddr_in),传sizeof(sockaddr)会导致结构体截断,sin_port变成 0
跨平台编译时容易漏掉的关键差异
Linux 和 Windows 的头文件、初始化、关闭方式不同,混用会导致编译失败或运行时 crash,不是“功能差不多就能跑”。
- Linux 编译加
-lpthread(如用多线程),Windows 链接ws2_32.lib,命令行写成g++ server.cpp -lws2_32 - Windows 必须在
socket()前调WSAStartup(MAKEWORD(2,2), &wsaData),退出前调WSACleanup();Linux 完全不用 - 关闭 socket:Linux 用
close(sockfd),Windows 用closesocket(sockfd);混用会泄漏句柄或 crash - 错误检查别只看返回值:Linux 查
errno,Windows 查WSAGetLastError(),二者互不兼容
真正卡住性能的,往往不是模型选择,而是地址没填对、字节序搞反、addr_len 没重置、跨平台清理没做全——这些细节错一个,通信就静默失效,比超时还难排查。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











