Windows平台recvfrom无法接收全部UDP数据包的原因及解决
问题根源:套接字接收超时配置不合理
你的问题既不是UDP协议特性导致的丢包(Wireshark已确认所有数据包都到达主机),也不是recvfrom函数的已知问题,完全是套接字的SO_RCVTIMO超时设置不当引发的代码逻辑问题。
具体原因分析
- Windows下
SO_RCVTIMO的行为特性
在Windows系统中,SO_RCVTIMO用于设置阻塞套接字的接收超时时间。当你将超时设为1秒(1000毫秒)时,recvfrom会最多等待1秒来读取数据:
- 如果1秒内没有数据包到达,函数返回
-1并触发WSAETIMEDOUT错误; - 但如果数据包刚好在超时触发的临界时刻到达(比如
recvfrom即将返回超时的瞬间,数据包进入了套接字接收缓冲区),此时recvfrom已经触发超时返回,你的代码会直接return S_OK,没有尝试再次读取缓冲区中已存在的数据包。
- 超时设置与发包频率的冲突
你的数据包是每秒发送1个,当超时时间设为1秒时,这种“数据包到达但错过读取窗口”的临界情况发生概率会被放大:一旦某次recvfrom因超时而返回,而此时刚好有数据包已到达缓冲区,这个包就会被遗留。如果后续调用又因为等待下一个包超时,就会导致多个包堆积遗漏,最终表现为少接收约20个包。
而将超时改为100000微秒(即100毫秒)后,recvfrom会更频繁地检查接收缓冲区,大大降低了错过读取窗口的概率,所有到达的数据包都能被及时读取。
结合你的代码看问题点
你的代码中,当recvfrom返回WSAETIMEDOUT时直接返回S_OK,没有处理“缓冲区中可能已有数据包”的情况:
socklen_t sockaddr_in_len = sizeof(sockaddr_in); nResult = recvfrom(s, buff, buffLen, 0, (sockaddr*)sender, &sockaddr_in_len); SAFE_UINT ulError = WSAGetLastError(); if (nResult == -1) { if(ulError != WSAETIMEDOUT) { Sleep(100); switch (ulError) { case WSAECONNRESET: //Print error return S_OK; case WSAEINTR: //Print error return S_OK; case WSAENOTCONN: //Print error return S_OK; default: //Print error return S_OK; } } else { //Print error return S_OK; // 这里直接返回,未检查缓冲区是否有未读数据 } } else if (nResult == 0) { //Print error return S_OK; }
虽然缩短超时时间解决了问题,但更严谨的做法是:在超时返回后,尝试以非阻塞模式再次调用recvfrom,读取缓冲区中可能存在的数据包,彻底避免遗漏。
内容的提问来源于stack exchange,提问作者Shani.M
相关产品推荐
相关产品推荐

