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

时间:2010-08-31 21:54:18

标签: sockets network-programming udp boost-asio multicast

我使用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和a上的机器上部署了它 Mac OSX Leopard。我很高兴所有人都能进行多播 平台使用从示例派生的代码开箱即用。

我遇到问题的时候,是我断开网线的时候。的 当然,断开电缆总是会引起问题;)但是那里 微妙的差异让我发疯。

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

观察到的行为:

- 显然,接收器运行的机器不再接收 消息。这是预期的;)

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

- 当拔掉网线的机器运行Mac OSX时, 发件人继续发送没有显示错误信息,但是 同一台机器上的接收器不再接收。在你问之前,我 选中NOT不设置禁用环回选项。

- 当拔掉网线的机器运行Linux时, 发送程序失败并出现提升::错误“网络无法访问”。明显, 由于发送方无法发送数据,因此接收方无法接收 任何事情。

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

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

我发现在Mac OSX上我收到“网络无法访问”错误 重新插入网络电缆并且DHCP没有重新插入时 但是获得了一个新的IP地址。

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

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

2 个答案:

答案 0 :(得分:1)

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

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

对于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解决方案感兴趣,我不想过多地向这个页面发送垃圾邮件。

答案 1 :(得分:0)

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

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

在Linux中,当您断开电缆连接时,以太网接口会丢失其IPv4地址,删除到239.255.0.1的路由,环回接口未配置为发送到127以外的任何内容。。 *,所以你得到一个错误。

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

要尝试的另一件事是在OS X上使用自我分配的IP,因此您拥有相同的IP&amp;电缆连接或断开时的路由。