うさぎ駆動開発

UWP, Xamarin.Macを中心によしなしごとを書いていきます。

UWP でSSDPパケットを送信する

SSDPとはSimple Service Discovery Protocolのことで,要はLAN内部のデバイスを見つけるためのUPnPの一部です。

UPnPはデバイスと複数プロトコルでやりとりをしていきますが,最初のDiscoveryフェーズで使い,UDP/1900です。デバイスからの返答はNOTIFY,以降はTCPで通信します。

UWP でLAN内のデバイスを見つけに行ってみましょう。RFCないので,これを参考にします。

draft-cai-ssdp-v1-03 - Simple Service Discovery Protocol/1.0

SSDP送信部の実装

ざっくりと,こういう感じのメソッドを定義します。UDPなので,投げたパケットに対する応答は来ません。ただひたすら誰かが飛ばしたものをキャッチするイメージでしょうか。

private async Task<string[]> SendSSDP(CancellationToken token)
{
    // マルチキャストグループ
    var multicastIp = new HostName("239.255.255.250");
    var waitForReceiver = new ManualResetEventSlim(false);
    var results = new List<string>();

    using (var socket = new DatagramSocket())
    {
        socket.MessageReceived += (s, e) =>
        {
            using (var reader = e.GetDataReader())
            {
                var ret = reader.ReadString(reader.UnconsumedBufferLength);
                // M-SEARCH は自分が送出したものなので無視してよい
                if (ret.StartsWith("M-SEARCH")) return;
                // 同じ情報なら捨ててしまう
                if (results.Contains(ret)) return;

                results.Add(ret);
                // 欲しいものが飛んでくるまで待つ,ここでは3件以上来たらよしとする
                if (results.Count > 3)
                    waitForReceiver.Set();
            }
        };
        await socket.BindServiceNameAsync("1900");
        socket.JoinMulticastGroup(multicastIp);

        var request = string.Concat(
            "M-SEARCH * HTTP/1.1\r\n",
            "HOST: 239.255.255.250:1900\r\n",
            "MAN: \"ssdp: discover\"\r\n",
            "MX: 2\r\n",
            "ST: ssdp:all\r\n\r\n");
        var reqbytes = Encoding.UTF8.GetBytes(request).AsBuffer();
        using (var stream = await socket.GetOutputStreamAsync(multicastIp, "1900"))
        {
            await stream.WriteAsync(reqbytes);
        }
        waitForReceiver.Wait(5000, token);
    }
    return results.ToArray();
}

メッセージはHTTPヘッダと似ています。改行文字は\r\nです。ここでは ST: ssdp:all メッセージを送信しているので,受け取ったすべてのデバイスが返答してきます。

お目当てのデバイスがすぐに返答してくるとはかぎりません。待ち時間 MX , receiverのタイムアウトは適当に調整する必要があります。

アプリケーションに組み込んで表示してみます。 f:id:ailen0ada:20160317224052p:plain

PCで動かすと socket.BindServiceNameAsync でたいてい例外になると思います。これはWindows PCではすでにUPnPデバイス探索が動作しているため,同じポートにソケットをバインドできないからです。

サンプルコードは下記に配置しました。

github.com