ぷろじぇくと、みすじら。

ご報告

Created at:

ガルパンはいいぞ

DNS-SD(Bonjour)をUniversal Windows Platformから利用してサービスを探す

Created at:

Windows 10からDNS-SD/mDNS、いわゆるAppleのBonjourをネイティブサポートするようになりました。
要するに何ができるかというとIPアドレスやホスト名を知らなくても同じネットワークにいるデバイスとサービスを探すことができる仕組みです。

例えば、iTunesが他のコンピューターのiTunes 共有ライブラリを見つけるといったことに利用されています。そのちょっと便利な機能をUWPのAPIを通して利用できるようになっています。

アプリからDNS-SDでサービスを探す

さて、どうやって使うかですがまあまあ簡単で、通常のデバイスを列挙するのと同様にDeviceInformation クラスを利用します……が、列挙しようと思ってFindAllAsyncメソッドを使うと何も返ってこず(awaitすると進まない)、暫くするとエラーが出ます。

この挙動が正しいのかどうかよくわかりませんが、ともかく何やらうまく動きません。

そこでDeviceInformation.CreateWatcher メソッドを利用します。
このメソッドはデバイスがネットワークに追加されたり削除されたりというのを監視する方法です。この方法でならば列挙できます。

Watcherを作ってStartメソッドを呼び、監視を始めるとデバイスが見つかりAddedイベントが呼び出されますのでそこで適当に情報を保持しておけばよいです。
ちなみに監視して列挙終了時の EnumerationCompleted イベントが発生しないので完了を待つというのはやめた方がいいでしょう(多分それがFindAllAsyncが返ってこない原因な気がします)。

とりあえず、ネットワークにあるAndroid TV(正確にはAndroid TVが持つリモコン用サービス)を列挙するソースコードはこんな感じなります。

using Windows.Devices.Enumeration;

// ...

// DNS-SDのサービス名
var serviceName = "_androidtvremote._tcp";
// DNS-SDを利用する
var protocolId = "{4526e8c1-8aac-4153-9b16-55e86ada0e54}";
// デバイス検索文字列
var aqsFilter = $"System.Devices.AepService.ProtocolId:={protocolId} AND System.Devices.Dnssd.Domain:=\"local\" AND System.Devices.Dnssd.ServiceName:=\"{serviceName}\"";
// 取得するプロパティ
var properties = new[] {
    "System.Devices.Dnssd.HostName", // ホスト名 (自称)
    "System.Devices.IpAddress", // IPアドレスの配列(string[])
};

var watcher = DeviceInformation.CreateWatcher(aqsFilter, properties, DeviceInformationKind.AssociationEndpointService);
watcher.Added += (sender, args) =>
{
    Debug.WriteLine("Added: " + args.Id);
    foreach (var prop in args.Properties)
    {
        var value = prop.Value is string[] ? (String.Join(", ", (string[])prop.Value)) : prop.Value + "";
        Debug.WriteLine($"  - {prop.Key}: {value}");
    }
};
watcher.Updated += (sender, args) =>
{
    Debug.WriteLine("Updated: " + args.Id);
    foreach (var prop in args.Properties)
    {
        var value = prop.Value is string[] ? (String.Join(", ", (string[])prop.Value)) : prop.Value + "";
        Debug.WriteLine($"  - {prop.Key}: {value}");
    }
};
watcher.Start();

実行するとこんな感じにホスト名やIPアドレスなどの情報が取れます。

Added: DnsSd#KJ-55X8500C._androidtvremote._tcp.local#0
  - System.ItemNameDisplay: KJ-55X8500C
  - System.Devices.DeviceInstanceId: 
  - System.Devices.Icon: 
  - System.Devices.GlyphIcon: 
  - System.Devices.InterfaceEnabled: 
  - System.Devices.IsDefault: 
  - System.Devices.PhysicalDeviceLocation: 
  - System.Devices.Aep.CanPair: False
  - System.Devices.Aep.IsPaired: False
  - {A35996AB-11CF-4935-8B61-A6761081ECDF} 18: 
  - {A35996AB-11CF-4935-8B61-A6761081ECDF} 13: 
  - System.Devices.Dnssd.HostName: Android.local
  - System.Devices.IpAddress: 192.168.1.100

アプリはこの情報をもとにサーバー(サービス)に接続すれば、相手の名前をあらかじめ知っていなくてもアクセスできる、というわけです。

手順をおさらいすると、DeviceWatcherを作るCreateWatcherメソッドの引数に渡すAQS文字列と呼ばれる検索フィルター文字列に、
DNS-SDプロトコル({4526e8c1-8aac-4153-9b16-55e86ada0e54})を使うことを指定して、さらにDNS-SDのサービス名(_daap.tcpなど)を指定して、検索を実行すれば見つかるという形になっています。例えばサービス名に_daap.tcpを指定するとネットワーク上のiTunesの共有ライブラリを探すことができます。

ちなみにこのUniversalなAPIはいわゆる従来のデスクトップアプリケーションからも呼び出すことができます。

制限事項

得られたホスト名(.localで終わる名前)をHttpClientなどに渡しても名前解決されますが、たまに失敗するのでIPアドレスを使った方がいいかもしれません。

なお、どうやら試した範囲ではサービス名を指定せずにネットワークに存在するサービスをすべて列挙する、ということはできないようです。System.Devices.Dnssd.ServiceNameを指定しないで検索すると何も返ってこないことに注意が必要です。

また、Hyper-Vが有効の場合にも動作しないという話もあります。