You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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: 连接尝试失败,因为连接方在一段时间后未正确响应,或已建立的连接失败因为连接主机未能响应。


问题根源与解决方案

核心问题分析

  1. 绑定时机错误:发送广播后再绑定Socket,系统会分配新的随机端口,但设备的响应是发送到你发送广播时使用的源端口,而非新端口,导致recvfrom无法接收。
  2. 字节序未转换:EtherNet/IP协议要求多字节字段使用网络字节序(大端),原代码未做转换,可能导致设备无法解析广播消息,即使Wireshark能看到数据包,设备也不会响应。
  3. 不必要的广播选项关闭: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 12:10:39