Windows Store アプリでUDP Multicast Group通信

このブログでは、過去に、.NET Micro Frameworkや、Windows Phone 7、デスクトップアプリによるUDP Multicast Group通信について解説しました。

そろそろ、Echonet-LiteとかにもWindows Storeアプリつなぎたいし、.NET Micro Frameworkや、他のPlatformのスマホアプリとも連携したいな…ってことで、Windows Storeアプリで、UDP Multicast Group通信をする方法を解説します。

UDP Multicast Group通信って何?って方、同じローカルネットにつながっている端末同士で、UDP/IPを使って同報通信する為のプロトコルです。インターネット上のサービスにアクセスする場合に指定するURLなど必要なく、あらかじめ知っているグループアドレス(224.0.0.0 ~ 239.255.255.255)とグループポート番号に対して、仲間入りすれば、そのグループアドレス、ポート番号にデータを送るだけで、仲間入りしているデバイスがデータを受信できるという、単に情報を発信してあとは受信したデバイスの自由的な通信ができます。WS-DiscoveryのようなAd-Hocに端末やサービスを見つけ出すプロトコルなどでも利用されています。イメージとしては、何かのパーティで、部屋のWi-Fiに、タブレットと複数のスマートフォンやタブレットをつないで、ビンゴゲームをするとか、複数名が自分の端末で自分の手札と山だけを見ながらカードゲームをし、傍からそのゲームの進行状況を見てる、なんていうアプリが作れるわけです。

早速本題に入ります。

Windows Storeアプリでは、WinRT APIのDatagramSocketを使ってUDP Multicast Group通信を行います。先ずは、Multicast GroupにJoinするところまでのコードです。

先ずは、クラスのメンバー変数として以下を定義しておきます。

DatagramSocket udpMCSocket;
DataWriter udpMCDataWriter;
string groupAddress = "2xx.xxx.xxx.xxx";
int groupPort = 50000;
string joinigMessage = "Hello Joined";

次に、グループにJoinするコードです。

    var groupIp = new HostName(groupAddress);
    udpMCSocket = new DatagramSocket();
    try
    {
        udpMCSocket.MessageReceived += udpMCSocket_MessageReceived;            // Multicast Groupに送信されたデータを受信するためのハンドラを登録
        await udpMCSocket.BindServiceNameAsync(groupPort.ToString());                   // グループポートを指定してバインド 
        udpMCSocket.JoinMulticastGroup(groupIp);                                                         // グループアドレスを指定してJoin

        udpMCDataWriter = new DataWriter(await udpMCSocket.GetOutputStreamAsync(groupIp, tbGroupPort.Text));     // グループへのデータ送信用DataWriterを作成
        var result= await SendMessageAsync(joiningMessage);    // グループにJoinしたことを通知するメッセージを送信
    }
    catch (Exception ex)
    {
        ...;
    }

SendMessageAsyncメソッドは以下の様なコードにします。

private async Task<uint> SendMessageAsync(string message)
{
    var messageBytes = System.Text.UTF8Encoding.UTF8.GetBytes(message);
    udpMCDataWriter.WriteBytes(messageBytes);
    return await udpMCDataWriter.StoreAsync();
}

周辺機器連携の投稿でも出てきましたが、WinRT APIのSocketなどを使った通信と同じパターンで、WriteXXXメソッドでデータを書き込み、StoreAsyncメソッドで実際に送信、といった流れです。こういうパターンでは、最初に2バイト、または4バイトで送りたいデータ長を先ず送った後に、データ本体を送るというスタイルをとることが多いですが、UDP通信なので、データ送信や到着順番の保証がなされない、複数のメッセージが飛び交ってデータ長とデータ本体の間に挟まってしまい、解析の手間が生じてしまうなどを、避けるために、文字列をUTF8でエンコーディングしてバイト列で送っています。この辺、一応気にかけておいてくださいね。

受信メソッドは、以下の様になります。

async void udpMCSocket_MessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
{
    var reader = args.GetDataReader();                    // 引数からDataReaderを取り出し
    uint size = reader.UnconsumedBufferLength;      // 受信データ長を取得
    try
    {
        var msgBytes = new byte[size];
        reader.ReadBytes(msgBytes);                         // 受信データをmsgBytesバッファに読み込み
        var message = UTF8Encoding.UTF8.GetString(msgBytes, 0, (int)size);   // UTF8でデコード

        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => {
            StringBuilder sb = new StringBuilder(tbReceivedMessage.Text);
            sb.AppendLine(args.RemoteAddress + "-" + message);        // どの端末から送られてきたか、RemoteAddressを見ればわかる
            sb.AppendLine();

            tbReceivedMessage.Text = sb.ToString();    // XAML上のTextBlockに 受信データを書き込み
        });
    }
    catch (Exception ex)
    {
        …
    }

周辺機器連携等で解説したDataWriter/DataReaderパターンでは、DataReaderのReadXXXメソッドをコールする前に、LoadAsyncメソッドをコールするようになっていましたが、DatagramSocketのMessageReceivedイベントでは、既にLoadAsyncがコールされたのと同じ状態なので、イベント引数で渡される受信データ長を調べてすぐに、ReadXXXメソッドでデータを受信します。
上のコードの中で、DispatcherのRunAsyncメソッドをコールして委譲している部分がありますが、このメソッドは、GUIを管理するスレッドの外から呼ばれるので、XAMLで定義されたUI要素に値を直接書き込む場合には、このパターンが必要です。

実際に動かすためには、Package.appxmanifestの機能タブを選択し、

  • インターネット(クライアント)
  • インターネット(クライアントとサーバー)
  • プライベートネットワーク(クライアントとサーバー)

の3つにチェックを入れてください。

以上です。アプリ色々、端末色々。ネットワークのサービスをマッシュアップすると面白くて便利なのと同様、アプリもマッシュアップすると面白ですよ。