Windows でサポートされる信頼性の高いマルチキャストを実現する方法にPGM(Pragmatic General Multicast) があります。仕事で使うかもしれないのでC#でサンプルプログラムを作成してみたので覚書を記載します。C#でプログラムを作成するにあたり参考にしたURLを記載します。

Reliable Multicast Programming (PGM)
http://msdn.microsoft.com/en-us/library/ms740125(v=vs.85).aspx
IP マルチキャスト
http://technet.microsoft.com/ja-jp/library/cc756156(v=ws.10).aspx

今回は1番目のURLに記載されている C++ での送信、受信プログラム を .NET に変換する形式で作成しました。

動作確認環境は次の通りです

  • Windows Server 2008 R2 間で送受信
  • .NET 4.0で作成したコンソールプログラム (.NET 4 である必要はありません。開発環境は Visual Studio 2010 なだけです。)
  • ネットワークアダプタに Reliable Multicast Protocol プロトコルインストール済み

XPの場合なMicrosoft Message Queuing (MSMQ) 3.0がインストールされている必要があります。

1. 送信プログラムの作成

送信を行うプログラムサンプルは次の通りです。重要な点はSocket 作成時に SocketTypeにRdm, プロトコルタイプに 113 をしている処理です。 113はPGMのプロトコル番号ですが、残念ながら列挙型のProtocolTypeにはPGMの値がないので、強引にキャストしてソケットを作成しています。

サンプルではマルチキャスト用のエンドポイントにコネクトしたあと、メッセージを1回送信して接続を終了しています。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;

namespace ConsoleSender
{
    /// <summary>
    /// 参考:http://msdn.microsoft.com/en-us/library/ms740090.aspx
    /// </summary>
    class Program
    {
        private static Socket _socket = null;
        static void Main(string[] args)
        {
            try
            {
                // 接続
                Connect();
                // 送信
                Send("hello");
                // 接続解除
                Disconnect();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
            Console.Write("Press Enter to exit.");
            Console.ReadLine();
        }

        /// <summary>
        /// 接続処理
        /// </summary>
        static void Connect()
        {
            // 113 は PGMのプロトコル番号
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Rdm, (ProtocolType)113);

            // ローカルのエンドポイントとバインド
            IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 0);
            _socket.Bind(localEndPoint);

            // マルチキャスト用のIP,ポートに接続(エンドポイントは送受信で同じであること)
            IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse("234.5.6.7"), 20000);
            _socket.Connect(remoteEndPoint);
        }
        static void Send(string message)
        {
            string s = string.Format("{0} from {1}", message, System.Net.Dns.GetHostName());
            byte[] data = Encoding.Unicode.GetBytes(s);

            SocketError socketError;
            _socket.Send(data, 0, data.Length, SocketFlags.None, out socketError);

            Console.WriteLine("Send Result:{0}", socketError);
        }
        /// <summary>
        /// 接続終了処理
        /// </summary>
        static void Disconnect()
        {
            _socket.Shutdown(SocketShutdown.Both);
            _socket.Disconnect(false);
            _socket.Close();
            _socket = null;
        }

    }
}

動作させる場合は管理者として実行する必要があります。管理者として実行していない場合 "アクセス許可で禁じられた方法でソケットにアクセスしようとしました"という例外が Bind メソッド実行時に発生します。

2. 受信プログラム

受信を行うプログラムのサンプルは次の通りです。送信プログラムと同様にSocket 作成時に SocketTypeにRdm, プロトコルタイプに 113 を指定しています。サンプルプログラムは処理を簡単にするため新しいセッションが作成された場合、メッセージを1回受信してセッションを終了しています。バインドするアドレスとポートは送信側が接続するエンドポイントと一致している必要があります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace ConsoleReceiver
{
    /// <summary>
    /// 参考:http://msdn.microsoft.com/en-us/library/ms740090.aspx
    /// </summary>
    class Program
    {
        static Socket _socket;
        static void Main(string[] args)
        {
            Console.WriteLine("プログラムを終了するには Ctrl-C で強制終了させてください。");

            Listen();
            Accept();
        }

        /// <summary>
        /// 受信用のサーバーソケット作成
        /// </summary>
        static void Listen()
        {
            // 113 は PGMのプロトコル番号
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Rdm, (ProtocolType)113);

            // PGMマルチキャスト用エンドポイントとバインド(送信プログラムがConnectするIP,ポートと同じ)
            IPEndPoint receiveEndPoint = new IPEndPoint(IPAddress.Parse("234.5.6.7"), 20000);
            _socket.Bind(receiveEndPoint);

            _socket.Listen(10);

        }
        /// <summary>
        /// リッスンしているサーバーソケットでAcceptを無限に実施。
        /// 送信プログラムから接続があった場合、セッションの処理
        /// (メッセージ受信)をReceiveメソッドで実施
        /// </summary>
        static void Accept()
        {
            while (true)
            {
                Socket receiveSocket = _socket.Accept();
                ThreadPool.QueueUserWorkItem(Program.Receive, receiveSocket);
            }
        }

        static void Receive(object o)
        {
            Socket receive = o as Socket;

            byte[] buffer = new byte[1024];
            int count = receive.Receive(buffer);
            string data = Encoding.Unicode.GetString(buffer, 0, count);
            Console.WriteLine("Receive Message :{0} from {1}", data, receive.RemoteEndPoint);

            // 今回は1回メッセージを受信したら受信セッションを終了する
            receive.Shutdown(SocketShutdown.Both);
            receive.Disconnect(false);
            receive.Close();
        }
    }
}

プログラムはAcceptメソッドで無限ループで新しいセッションの接続を待機しています。終了するには Ctrl-C で強制終了してください。

3. 受信がうまくいかない場合

ローカル環境で 送信、受信プログラムを起動した場合にうまく動作したのですが、別のマシン間でPGMを使用した送受信を行う場合、受信処理がうまくいかない(送信プログラムからメッセージを送信しても受信側のSocket.Acceptメソッドで受信用のSocketが返ってこない)現象が発生しました。 しかも受信プログラムと同一のマシンで送信プログラムを起動して受信を行うと、それ以降は別のマシンからのメッセージも受信できるようになるという現象でした。本現象は 機能の追加ウィザードを起動して メッセージキュー (メッセージキューサーバーとマルチキャスト サポート) をインストールすることで解決しました(下図参照)。

Reliable Multicast Protocol はメッセージキューをインストールした場合もインストールされます(下図参照)。Reliable Multicast Protocol を単独でインストールするだけでPGMプロトコル通信を行えると思っていたのですが、 別々のマシンで通信する場合、受信側ではメッセージキューのマルチキャストサポート が必要(or インストール時に必要な設定を行ってくれている) みたいです。

4. まとめ

説明は以上です。 MSDNには c++ を使ったコードの一部が公開されていたので、 C# で同じようなプログラムを作成してみました。今後使うことがあるかは不明ですが。。。

間違い、指摘点などありましたらご連絡ください。