【问题标题】:UDP code only receiving first response to a broadcastUDP 代码仅接收对广播的第一个响应
【发布时间】:2016-10-26 13:17:56
【问题描述】:

我正在尝试使用以下 c + Objective-c 代码与多个 SQL Server Browser 服务进行通信。

+ (void) doBrowseForServers
{
    // http://mbielanczuk.com/2013/07/browsing-network-for-sql-server-instances-in-c/
    // used as a reference.
    SOCKET sock;
    int broadcast = 1;

    struct timeval timeout;
    timeout.tv_sec  = 5; // 10 second timeout
    timeout.tv_usec = 0;

    if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
    {
        NSLog(@"MSSQLBrowserServiceClient failed to initialize socket.");
        return;
    }

    // Setting socket broadcast option.
    if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&broadcast, sizeof(broadcast)) == -1)
    {
        NSLog(@"MSSQLBrowserServiceClient failed to set broadcast option.");
        return;
    }

    // Set a timeout so that we do not wait forever.
    if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)) == -1)
    {
        NSLog(@"MSSQLBrowserServiceClient failed to set send timeout.", errno);
        return;
    }

    // Set a timeout so that we do not wait forever.
    if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) == -1)
    {
        NSLog(@"MSSQLBrowserServiceClient failed to set receive timeout.", errno);
        return;
    }

    if(sendBroadcast(sock, MSSQL_BROWSER_SERVICE_PORT))
    {
        NSString * response = [self reciveBroadcastRespondFromSocket: sock
                                                           usingPort: MSSQL_BROWSER_SERVICE_PORT];

        NSDate * lastSeen = [NSDate date];

        if(0 != response.length)
        {
           // Processing code omitted to focus on send/receive
        } // End of we had responses
    }
    else
    {
        NSLog(@"MSSQLBrowserServiceClient failed to browse for servers.");
    }
} // End of beginBrowseForServers

bool sendBroadcast(SOCKET sock, int port)
{
    struct sockaddr_in bcastAddr;
    memset(&bcastAddr, 0, sizeof(bcastAddr));
    bcastAddr.sin_family = AF_INET;
    bcastAddr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
    bcastAddr.sin_port = htons(port);
    char query = 0x02;
    if (sendto(sock, &query, sizeof(query), 0, (struct sockaddr *)&bcastAddr, sizeof(bcastAddr)) == -1)
    {
        return 0;
    }

    return 1;
}

+ (NSString*) reciveBroadcastRespondFromSocket: (SOCKET) sock
                                     usingPort: (int) port
{
    ssize_t recvBytes = 0;
    char buf[512] = {0x00};
    memset(buf,'\0', 512);

    NSMutableData * mutableData = [NSMutableData data];

    struct sockaddr_in recvAddr;
    memset(&recvAddr, 0, sizeof(recvAddr));
    recvAddr.sin_family = AF_INET;
    recvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    recvAddr.sin_port = htons(port);
    socklen_t recvAddrLen = sizeof(recvAddr);

    while ((recvBytes = recvfrom(sock, buf, sizeof(buf)-1, 0, (struct sockaddr*)&recvAddr, &recvAddrLen)) != -1)
    {
        [mutableData appendBytes: buf
                          length: recvBytes];
    }

    if(mutableData.length <= 3)
    {
        return nil;
    }

    char * bytes = (char*)[mutableData bytes];

    // Process code removed to focus on networking bits.
    // ....

    return @"";
}

问题是我似乎一次只处理一个服务器响应。如果我多次运行此代码,我最终可能每次都从不同的服务器接收结果(每个广播一个服务器),但我的理解是我应该通过单个广播从所有服务器接收 udp 数据报。我尝试增加超时时间,但每次广播我仍然只能从一台服务器接收结果。

谁能指出我做错了什么?

【问题讨论】:

    标签: objective-c c networking udp


    【解决方案1】:

    我相信罪魁祸首在while ((recvBytes = recvfrom 循环中。我认为它会因错误而终止。

    UDP 广播的一个典型“错误”是WSAECONNRESET。我不完全知道这意味着什么,无法访问有关目标端口的某些内容。尽管如此,这仍然是 UDP 广播中经常发生的错误。由于这个错误几乎是意料之中的,并且它与广播目标之一而不是您的代码有关,因此解决方案是忽略此特定错误并继续循环到下一个 recvfrom (您实际上是在运行代码时“执行”此操作多次)。一些操作系统允许将套接字配置为自动忽略WSAECONNRESET。例如,在 Windows 上为 WSAIoctl(SIO_UDP_CONNRESET)

    如果这对您没有帮助,请提供有关您的代码如何运行的更多信息:循环迭代次数、是否等待超时、哪个确切错误结束循环。

    【讨论】:

      【解决方案2】:

      你应该在 recvfrom 返回 -1 后检查 errno。这将指示是否没有数据可用并且由于超时(EAGAIN)而返回调用,或者是否发生了一些错误(例如,在您期望来自另一台服务器的数据时的第二次调用)。很可能您的 recvAddr 和/或 recvAddrLen 数据在第一次(成功)调用后被更改,并且在第二次调用时变得无效。您必须在每次调用时(重新)初始化这些变量。

      请注意,您使用 INADDR_ANY 值初始化 recvAddr,该值将在第一次调用后被服务器 IP 地址覆盖。如果 recvfrom 使用这个地址作为它应该只从这个 IP 地址接收的指示,它会解释你看到的行为。但是,标准的 recvfrom 实现不使用此地址值。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-07
        相关资源
        最近更新 更多