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のタイムアウトは適当に調整する必要があります。
アプリケーションに組み込んで表示してみます。
PCで動かすと socket.BindServiceNameAsync
でたいてい例外になると思います。これはWindows PCではすでにUPnPデバイス探索が動作しているため,同じポートにソケットをバインドできないからです。
サンプルコードは下記に配置しました。