2

我使用 UDP 多播使用 boost.Asio 制作了一个应用程序。我不认为这个问题是针对 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时,发送方继续发送,同一台机器上的接收方继续接收。未检测到错误。似乎windows有一个内在的回退回环?

- 拔下网线的机器运行Mac OSX时,发送方继续发送,不显示错误信息,但同一台机器上的接收方不再接收。在你问之前,我检查了不设置禁用环回选项。

- 拔下网线的机器运行Linux时,发送方失败,提示boost::error "Network is unreachable"。显然,由于发送方无法发送数据,因此接收方不再接收任何内容。

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

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

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

所以基本上:我怎样才能在 MacOSX 上实现本地客户端仍然可以从本地发件人接收?要么像我在 Linux 上那样检测网络丢失,要么像 Windows 一样欺骗它。

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

4

2 回答 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(使用socket(AF_NETLINK,SOCK_RAW,NETLINK_ROUTE))和Windows(使用NotifyAddrChange())下获得网络配置更改通知的等效(并且相当模糊)机制,如果它们是的话,我可以发布有帮助,但如果您只对 MacOS/X 解决方案感兴趣,我不想过多地向此页面发送垃圾邮件。

于 2010-12-11T23:50:47.873 回答
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 和路由。

于 2010-12-11T23:20:51.457 回答