首页 文章

Windows-Linux-Mac上的UDP套接字网络断开连接行为

提问于
浏览
2

我使用boost.Asio使用UDP多播创建了一个应用程序 . 我认为这个问题并不是特定于boost.Asio而是一般的套接字编程,因为boost.Asio的网络设施大多是套接字函数的包装器 .

我基于组播示例(http://www.boost.org/doc/libs/1_44_0/doc/html/boost_asio/example/multicast/receiver.cpp和〜/ sender.cpp)构建了应用程序,并将其部署在运行在Windows,Linux和带有OSX Leopard的Mac上的几台计算机上 . 我很高兴所有平台上的多播都是开箱即用的,并且代码来自这些示例 .

我遇到问题的地方,就是当我断开网线时 . 当然,断开电缆总会引起问题;)但是有一些微妙的差异让我发疯 .

我的测试设置总是如下:一台机器运行发送器和接收器,以查看同一台机器是否接收到自己的多播,另一台机器只运行接收器 . 我在运行发送器和接收器的机器上拉网线 .

观察到的行为:

  • 显然,接收器运行的机器不再接收任何消息 . 这是预期的;)

  • 当拔出网络电缆的机器运行窗口时,发送器继续发送并且同一台机器上的接收器继续接收 . 没有检测到错误 . 看来windows有一个内在的回退环回?

  • 当拔出网络电缆的机器运行Mac OSX时,发送器继续发送,不显示任何错误消息,但同一台机器上的接收器不再接收 . 在你问之前,我检查了不设置禁用环回选项 .

  • 当拔出网络电缆的机器运行Linux时,发送器失败并出现boost :: error“网络无法访问” . 显然,由于发送方无法发送数据,接收方不再接收任何内容 .

对于Linux,我可以通过捕获“无法访问”错误(或写入错误的字节数)并在我的代码中设置一个标志来伪造Windows的行为,随后将所有数据发送到127.0.0.1而不是多播地址 . 我会定期检查多播 endpoints 上的send_to是否仍会产生错误,以检测网络重新连接并返回多播 . 这就像一个魅力,因为接收器是bind()到inaddr_any,因此也在127.0.0.1上监听 .

对于Mac OSX,我无法注意到网络何时无法访问以保持本地计算机上接收器的服务 .

我发现在Mac OSX上,当网络电缆重新插入并且DHCP尚未获得新的IP地址时,我会立即收到“网络无法访问”错误 .

所以基本上:我如何在MacOSX上实现,本地客户端仍然可以从本地发送者接收?通过检测网络丢失,就像我在Linux上做的那样,或者通过欺骗它来表现得像Windows一样 .

我们非常感谢任何对网络编程有深入了解的人的建议 .

2 回答

  • 1

    当我遇到这个问题时,我的解决方案是安排在网络配置发生变化时从操作系统获取通知 . 当我的程序收到该通知时,它将等待几秒钟(希望确保网络配置已完成更改),然后拆除并重建其所有套接字 . 这很痛苦,但似乎运作得很好 .

    当然,当网络配置发生变化时,没有与操作系统无关的方式(我知道)从操作系统获得通知,因此我必须在每个操作系统下以不同方式实现它 .

    对于MacOS / X,我产生了一个单独的watch-the-network-config线程,如下所示:

    #include <SystemConfiguration/SystemConfiguration.h>
    
    void MyNetworkThreadWatcherFunc(void *)
    {
       SCDynamicStoreRef storeRef = NULL;
       CFRunLoopSourceRef sourceRef = NULL;
       if (CreateIPAddressListChangeCallbackSCF(IPConfigChangedCallback, this, &storeRef, &sourceRef) == noErr)
       {
          CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopDefaultMode);
    
          while(_threadKeepGoing)   // may be set to false by main thread at shutdown time
          {
             CFRunLoopRun();
          }
    
          // cleanup time:  release our resources
          CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopDefaultMode);
          CFRelease(storeRef);
          CFRelease(sourceRef);
        }
     }
    

    还有这个设置/支持代码,从上面的函数调用:

    static OSStatus MoreSCError(const void *value) {return MoreSCErrorBoolean(value != NULL);}
    static OSStatus CFQError(CFTypeRef cf) {return (cf == NULL) ? -1 : noErr;}
    static void CFQRelease(CFTypeRef cf) {if (cf != NULL) CFRelease(cf);}
    
    // Create a SCF dynamic store reference and a corresponding CFRunLoop source.  If you add the
    // run loop source to your run loop then the supplied callback function will be called when local IP
    // address list changes.
    static OSStatus CreateIPAddressListChangeCallbackSCF(SCDynamicStoreCallBack callback, void *contextPtr, SCDynamicStoreRef *storeRef, CFRunLoopSourceRef *sourceRef)
    {
       OSStatus                err;
       SCDynamicStoreContext   context = {0, NULL, NULL, NULL, NULL};
       SCDynamicStoreRef       ref = NULL;
       CFStringRef             patterns[2] = {NULL, NULL};
       CFArrayRef              patternList = NULL;
       CFRunLoopSourceRef      rls = NULL;
    
       // Create a connection to the dynamic store, then create
       // a search pattern that finds all entities.
       context.info = contextPtr;
       ref = SCDynamicStoreCreate(NULL, CFSTR("AddIPAddressListChangeCallbackSCF"), callback, &context);
       err = MoreSCError(ref);
       if (err == noErr)
       {
          // This pattern is "State:/Network/Service/[^/]+/IPv4".
          patterns[0] = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4);
          err = MoreSCError(patterns[0]);
          if (err == noErr)
          {
             // This pattern is "State:/Network/Service/[^/]+/IPv6".
             patterns[1] = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv6);
             err = MoreSCError(patterns[1]);
          }
       }
    
       // Create a pattern list containing just one pattern,
       // then tell SCF that we want to watch changes in keys
       // that match that pattern list, then create our run loop
       // source.
       if (err == noErr)
       {
           patternList = CFArrayCreate(NULL, (const void **) patterns, 2, &kCFTypeArrayCallBacks);
           err = CFQError(patternList);
       }
       if (err == noErr) err = MoreSCErrorBoolean(SCDynamicStoreSetNotificationKeys(ref, NULL, patternList));
       if (err == noErr)
       {
           rls = SCDynamicStoreCreateRunLoopSource(NULL, ref, 0);
           err = MoreSCError(rls);
       }
    
       // Clean up.
       CFQRelease(patterns[0]);
       CFQRelease(patterns[1]);
       CFQRelease(patternList);
       if (err != noErr)
       {
          CFQRelease(ref);
          ref = NULL;
       }
       *storeRef = ref;
       *sourceRef = rls;
    
       return err;
    }
    
    
    static void IPConfigChangedCallback(SCDynamicStoreRef /*store*/, CFArrayRef /*changedKeys*/, void *info)
    {
       printf("Network config changed!  Place code here to send a notification to your main thread, telling him to close and recreate his sockets....\n");
    }
    

    在Linux下(使用套接字(AF_NETLINK,SOCK_RAW,NETLINK_ROUTE)))和Windows(使用NotifyAddrChange())获取网络配置更改通知的等效(也是相当模糊)机制,如果它们是,我可以发布很有帮助,但如果您只对MacOS / X解决方案感兴趣,我不想太多垃圾邮件 .

  • 0

    我认为在Windows中发生的事情是,即使断开电缆连接,Windows仍然保持以太网接口打开,因为您连接了一些套接字,并且您要发送的multicast_address保持有效 . Windows也可能会更改发送方/接收方正在使用的接口,因此更改在套接字级别是透明的 .

    我认为在OS X中发生的事情是,当您断开电缆时,发送器会多播到环回接口,但接收器仍然连接到断开的以太网接口 . OS X也可能正在配置发送方发送给的自我分配的IP,但接收方仍在侦听旧的DHCP IP .

    在Linux中,当您断开电缆连接时,以太网接口会失去它's IPv4 address, removes routes to 239.255.0.1, the loopback interface isn' t配置为发送任何内容在127 ... *之外,所以你得到一个错误 .

    也许解决方案是定期重新加入OS X接收器上的组? (也许你还需要定期重建发送者的 endpoints . )

    另一件事是在OS X上使用自我分配的IP,因此在连接或断开电缆时,您具有相同的IP和路由 .

相关问题