UDP广播发送成功但recvfrom超时问题求助(附代码)
UDP广播发送成功但recvfrom超时问题排查与解决
我尝试通过Socket发送UDP广播消息,接收网络中响应设备的回复。消息已成功发送,在Wireshark中可看到目标为源IP的响应数据包,但调用recvfrom函数始终出现超时(Winsock错误10060)。已尝试多种Socket选项与绑定组合,仍无法解决。当前收发代码如下:
// Ethernet/IP Encapsulation Header struct __attribute__((__packed__)) EnipEncapHeader { uint16_t command; uint16_t length; uint32_t session_handle; uint32_t status; uint64_t ctx_64; uint32_t options; }; int main() { // initialize winsock WSADATA wsaData; int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); // create a udp socket int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { printf("ERROR opening socket"); return 1; } // set the broadcast flag int broadcastEnable = 1; setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (char *)&broadcastEnable, sizeof(broadcastEnable)); // Construct the message struct EnipEncapHeader header; header.command = 0x63; header.length = 0x0000; header.session_handle = 0x00000000; header.status = 0x0000; header.ctx_64 = (uint64_t)0; header.options = 0x0000; // send to 255.255.255.255 on port 44818 struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr("255.255.255.255"); servaddr.sin_port = htons(44818); // send the packet int rc = sendto(sockfd, (const char*)&header, sizeof(header), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); if (rc < 0) { printf("ERROR sending packet, %d, %d", rc, WSAGetLastError()); return 1; } // clear the broadcast flag broadcastEnable = 0; setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (char *)&broadcastEnable, sizeof(broadcastEnable)); // set the timeout to 5 seconds struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0; setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); // bind the socket to any address struct sockaddr_in myaddr; memset(&myaddr, 0, sizeof(myaddr)); myaddr.sin_family = AF_INET; myaddr.sin_addr.s_addr = htonl(INADDR_ANY); myaddr.sin_port = htons(0); bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr)); // recieve from any address on the socket struct sockaddr_in from; int fromlen = sizeof(from); char buf[1024]; rc = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fromlen); if (rc < 0) { printf("ERROR recieving packet, %d, %d", rc, WSAGetLastError()); return 1; } else { printf("Received %d bytes from %s:%d", rc, inet_ntoa(from.sin_addr), ntohs(from.sin_port)); } return 0; }
运行后出现的错误:
ERROR recieving packet, -1, 10060
Winsock Error 10060: 连接尝试失败,因为连接方在一段时间后未正确响应,或已建立的连接失败因为连接主机未能响应。
问题根源与解决方案
核心问题分析
- 绑定时机错误:发送广播后再绑定Socket,系统会分配新的随机端口,但设备的响应是发送到你发送广播时使用的源端口,而非新端口,导致recvfrom无法接收。
- 字节序未转换:EtherNet/IP协议要求多字节字段使用网络字节序(大端),原代码未做转换,可能导致设备无法解析广播消息,即使Wireshark能看到数据包,设备也不会响应。
- 不必要的广播选项关闭:UDP Socket开启广播选项不影响接收普通UDP数据包,无需关闭。
修正后的代码
// Ethernet/IP Encapsulation Header struct __attribute__((__packed__)) EnipEncapHeader { uint16_t command; uint16_t length; uint32_t session_handle; uint32_t status; uint64_t ctx_64; uint32_t options; }; // Windows下64位字节序转换辅助函数 uint64_t htobe64(uint64_t value) { #if _BYTE_ORDER == _LITTLE_ENDIAN return ((value & 0x00000000000000FFULL) << 56) | ((value & 0x000000000000FF00ULL) << 40) | ((value & 0x0000000000FF0000ULL) << 24) | ((value & 0x00000000FF000000ULL) << 8) | ((value & 0x000000FF00000000ULL) >> 8) | ((value & 0x0000FF0000000000ULL) >> 24) | ((value & 0x00FF000000000000ULL) >> 40) | ((value & 0xFF00000000000000ULL) >> 56); #else return value; #endif } int main() { // initialize winsock WSADATA wsaData; int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { printf("WSAStartup failed: %d\n", iResult); return 1; } // create a udp socket int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { printf("ERROR opening socket, %d\n", WSAGetLastError()); WSACleanup(); return 1; } // set the broadcast flag int broadcastEnable = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (char*)&broadcastEnable, sizeof(broadcastEnable)) < 0) { printf("ERROR setting SO_BROADCAST, %d\n", WSAGetLastError()); closesocket(sockfd); WSACleanup(); return 1; } // 提前绑定Socket:确保发送和接收使用同一端口 struct sockaddr_in myaddr; memset(&myaddr, 0, sizeof(myaddr)); myaddr.sin_family = AF_INET; myaddr.sin_addr.s_addr = htonl(INADDR_ANY); myaddr.sin_port = htons(0); // 让系统自动分配固定端口 if (bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) { printf("ERROR binding socket, %d\n", WSAGetLastError()); closesocket(sockfd); WSACleanup(); return 1; } // 设置5秒接收超时 struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)) < 0) { printf("ERROR setting RCVTIMEO, %d\n", WSAGetLastError()); closesocket(sockfd); WSACleanup(); return 1; } // 构造消息:转换为网络字节序 struct EnipEncapHeader header; header.command = htons(0x63); header.length = htons(0x0000); header.session_handle = htonl(0x00000000); header.status = htonl(0x00000000); header.ctx_64 = htobe64(0); header.options = htonl(0x00000000); // 广播目标地址 struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr("255.255.255.255"); servaddr.sin_port = htons(44818); // 发送广播包 int rc = sendto(sockfd, (const char*)&header, sizeof(header), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); if (rc < 0) { printf("ERROR sending packet, %d, %d\n", rc, WSAGetLastError()); closesocket(sockfd); WSACleanup(); return 1; } printf("Sent %d bytes of broadcast\n", rc); // 接收响应 struct sockaddr_in from; int fromlen = sizeof(from); char buf[1024]; rc = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fromlen); if (rc < 0) { printf("ERROR recieving packet, %d, %d\n", rc, WSAGetLastError()); closesocket(sockfd); WSACleanup(); return 1; } else { printf("Received %d bytes from %s:%d\n", rc, inet_ntoa(from.sin_addr), ntohs(from.sin_port)); } // 清理资源 closesocket(sockfd); WSACleanup(); return 0; }
关键修改说明
- 提前绑定Socket:在发送广播前完成绑定,保证发送和接收使用同一个端口,设备的响应能正确送达。
- 修复字节序问题:所有多字节字段转换为网络字节序,确保设备能正确解析广播消息。
- 移除不必要的广播选项关闭:保留SO_BROADCAST选项不影响接收操作。
- 完善错误处理与资源清理:增加各步骤的错误检查,程序退出前关闭Socket并清理Winsock资源。
内容的提问来源于stack exchange,提问作者lcs
相关产品推荐
相关产品推荐

