UDP通信中服务端必须bind绑定本地地址(如INADDR_ANY+端口)才能接收数据,客户端可不绑定而由系统随机分配端口;但若需双向通信(如请求-应答),客户端也应bind以固定源端口并接收响应。

UDP通信必须先处理套接字创建和地址绑定
UDP是无连接协议,服务端必须显式绑定本地地址才能接收数据,客户端则可不绑定直接发送。但若客户端也调用 bind(),就能固定源端口、支持接收响应——这点常被忽略,导致“发出去收不到回包”。
关键点:
- 服务端务必用
bind()绑定INADDR_ANY和指定端口,否则recvfrom()会失败(错误码WSAEINVAL或EADDRNOTAVAIL) - 客户端若需双向通信(如请求-应答),建议也调用
bind();否则系统随机分配临时端口,服务端无法准确回传 - Windows 下需先调用
WSAStartup(),Linux 下无需,但头文件和库链接不同:g++ -o server server.cpp -lws2_32(Win) vsg++ -o server server.cpp(Linux)
sendto 和 recvfrom 的地址参数不能写错
sendto() 和 recvfrom() 都要传入 sockaddr_in* 类型的地址结构,但用途相反:前者填目标地址(服务端 IP+端口),后者填缓冲区用于接收发端地址(自动填充)。常见错误是把两者混用或未初始化 sin_zero 字段。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 发送前用
memset(&dest_addr, 0, sizeof(dest_addr))清零,再设置sin_family = AF_INET、sin_port = htons(8080)、sin_addr.s_addr = inet_addr("127.0.0.1") - 接收时声明
sockaddr_in src_addr和socklen_t addrlen = sizeof(src_addr),recvfrom()会写入实际来源地址,后续sendto()可复用该结构回包 - 注意
htons()和inet_addr()返回值可能为INADDR_NONE,需检查是否解析失败
UDP没有内置重传和顺序保证,应用层得自己兜底
一个 sendto() 调用对应一个 UDP 数据报,但不保证送达、不保证顺序、不保证不重复。典型表现是:客户端发 10 次,服务端只收到 7 次,且顺序错乱;或同一请求被处理两次。
简易应对方式(非生产级):
- 加简单消息头,如前 4 字节存递增序号(
uint32_t seq),服务端打印时可观察丢包/乱序 - 客户端每次发送后
recvfrom()等待服务端 ACK(例如返回相同 seq),超时则重发(最多 3 次) - 服务端收到重复 seq 不重复处理,直接回 ACK —— 这要求客户端在重发时保持 seq 不变
- 别用
std::string直接 send,因内部可能含 \0;改用data.c_str()+data.length(),或统一用std::vector<char></char>
关闭资源时记得 close() 和 closesocket()
Linux 用 close(),Windows 必须用 closesocket();混用会导致句柄泄漏甚至程序崩溃。更隐蔽的问题是:服务端未关闭套接字就退出,下次启动可能报 Address already in use(端口被占用)。
稳妥做法:
- 所有 socket 创建后立即设为非阻塞或加超时(
setsockopt(..., SO_RCVTIMEO, ...)),避免recvfrom()卡死 - 使用 RAII 封装(如自定义
UDPSocket类),构造时创建,析构时自动closesocket()/close() - Windows 下退出前必须调
WSACleanup(),否则再次运行可能初始化失败
UDP 简单,但每个细节都容易漏——尤其是地址清零、端口复用、跨平台关闭方式,这几个点踩一次调试半小时。


















