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 で実行されているいくつかのマシンで。例から派生したコードを使用して、すべてのプラットフォームでマルチキャストがすぐに機能することを非常に嬉しく思います。

問題が発生するのは、ネットワーク ケーブルを外したときです。もちろん、ケーブルを外すと常に問題が発生します ;) しかし、私を夢中にさせる微妙な違いがあります。

私のテスト セットアップは常に次のとおりです。送信側と受信側を実行する 1 台のマシンで、同じマシンが独自のマルチキャストを受信するかどうかを確認し、別のマシンは受信側のみを実行します。送信機と受信機を実行しているマシンのネットワーク コードを引っ張ります。

観察された動作:

-明らかに、受信機が実行されているマシンは、それ以上メッセージを受信しません。それは予想されていたことです;)

- ネットワーク ケーブルが抜かれているマシンが Windows を実行している場合、送信側は送信を続け、同じマシンの受信側は受信を続けます。エラーは検出されませんでした。Windowsにはループバックへの本質的なフォールバックがあるようです?

-ネットワーク ケーブルが抜かれたマシンが Mac OSX を実行している場合、送信者はエラー メッセージを表示せずに送信を続けますが、同じマシンの受信者はそれ以上受信しません。あなたが尋ねる前に、ループバックを無効にするオプションを設定しないようにチェックしました。

- ネットワーク ケーブルが抜かれているマシンが Linux を実行している場合、送信側は boost::error "Network is unreachable" で失敗します。明らかに、送信者はデータを送信できないため、受信者は何も受信しません。

Linux の場合、「到達不能」エラー (または間違ったバイト数の書き込み) をキャッチし、コードにフラグを設定して、すべてのデータをマルチキャスト アドレスではなく 127.0.0.1 に送信することで、Windows の動作を偽装できます。マルチキャスト エンドポイントの send_to がまだエラーを生成しているかどうかを定期的にチェックして、ネットワークの再接続を検出し、マルチキャストに戻ります。レシーバーは inaddr_any への bind() であり、したがって 127.0.0.1 もリッスンするため、これは魅力のように機能します。

Mac OSX の場合、ローカル マシン上のレシーバーのサービスを維持するためにネットワークに到達できなくなったときに、それを通知する手段がありません。

Mac OSX では、ネットワーク ケーブルが再接続され、DHCP がまだ新しい IP アドレスを取得していないときに、「ネットワークに到達できません」というエラーが一時的に発生することがわかりました。

基本的に:MacOSXでローカルクライアントがローカル送信者から受信できるようにするにはどうすればよいですか? Linux で行っているようにネットワークの損失を検出するか、それをだまして Windows のように振る舞わせます。

私よりもネットワーク プログラミングに詳しい方からのアドバイスをお待ちしております。

4

2 に答える 2

1

この問題が発生したときの解決策は、ネットワーク構成が変更されたときにOSから通知を受け取るように手配することでした。私のプログラムがその通知を受け取ったとき、それは数秒待ってから(うまくいけばネットワーク構成の変更が完了したことを確認するために)、次にすべてのソケットを破棄して再構築します。苦痛ですが、かなりうまくいくようです。

もちろん、ネットワーク構成が変更されたときにOSから通知を受け取る(私が知っている)OSに依存しない方法はないので、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(socket(AF_NETLINK、SOCK_RAW、NETLINK_ROUTE)を使用)およびWindows(NotifyAddrChange()を使用)でnetwork-config-changed通知を取得するための同等の(そしてかなりあいまいな)メカニズムがあります。役に立ちましたが、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 レシーバーで定期的にグループに再参加することでしょうか? (また、送信者のエンドポイントを定期的に再構築する必要がある場合もあります。)

もう 1 つの試みは、OS X で自己割り当て IP を使用することです。これにより、ケーブルが接続されているときも切断されているときも、同じ IP とルートを使用できます。

于 2010-12-11T23:20:51.457 に答える