こんばんわ、久しぶりに真面目に技術ネタでブログを書いてみます。
SignalR という非同期でリアルタイムな双方向通信を実現するライブラリが面白そうなので試してみました。作者は ASP.NET チームの Damian Edwards 氏と David Fowler 氏です。
ソースコードは GitHub で管理されていますが、NuGet で一式が公開されているので便利ですね。
とりあえず空の ASP.NET アプリケーションを作成します。SignalR は Web Forms や MVC などに関係なく動作するので、別にどれでも構わないのですが。
作成出来たら NuGet を使って SignalR をインストールします。パッケージ ID は「SignalR」なので、コンソールからでも GUI からでも好きな方を選んでください。
これで SignalR が使える状態になったので、Hub を作成していきます。Hub というのは API のエンドポイントとして考えておけばいいと思います。
と言っても ASP.NET Web API などの REST な API とは作り方も考え方も全く違うので、説明は難しい感じですがコードを見た方が理解できます。とりあえず SampleHub というクラスを作成して、属性やら継承クラスを追加しました。
[HubName("sample")] public class SampleHub : Hub { }
これが SignalR の Hub の基本的な実装です。次はクライアントサイドの準備をするので適当な HTML を追加して、いくつか JavaScript ファイルを参照追加しておきます。
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript" src="Scripts/jquery-1.6.4.js"></script> <script type="text/javascript" src="Scripts/jquery.signalR.js"></script> <script type="text/javascript" src="signalR/hubs"></script> </head> <body> </body> </html>
クライアントサイドのテンプレはこんな感じです。signalR/hubs は SignalR が自動的に生成してくれるので、必要なものですが深く考えないでも OK です。
まずはクライアントサイドから値の取得をしてみます。とりあえず以下のような GetValues というメソッドを Hub に追加しました。
[HubName("sample")] public class SampleHub : Hub { public IEnumerable<string> GetValues() { return new[] { "ほげほげ", "ふがふが", "はうはう" }; } }
単純に文字列配列を返しているだけです。次はクライアントサイドになります。
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript" src="Scripts/jquery-1.6.4.js"></script> <script type="text/javascript" src="Scripts/jquery.signalR.js"></script> <script type="text/javascript" src="signalR/hubs"></script> <script type="text/javascript"> $(function () { var sample = $.connection.sample; $.connection.hub.start(function () { sample.getValues().done(function (values) { alert(values); }); }); }); </script> </head> <body> </body> </html>
サーバ側に比べてちょっとコードが増えました。細かく見ていきます。
var sample = $.connection.sample;
$.connection.sample は SignalR が自動的に作成してくれる SampleHub のラッパーオブジェクトです。サーバ側の SampleHub に対しての処理はこのオブジェクトを経由して行います。
$.connection.hub.start(function () { sample.getValues().done(function (values) { alert(values); }); });
まだサーバとは接続されていないので $.connection.hub.start メソッドを呼び出して接続を開始しますが、引数に関数を渡すことで接続が完了時に行う処理を追加できます。
そして、接続が完了した時に sample.getValues メソッドを呼び出していますが、このメソッドは SampleHub に実装した GetValues メソッドと同じになります。しかし、非同期でメソッドの呼び出しが行われるので、done メソッドを使って完了時に呼び出される関数を指定しておきます。
これで実行するとアラートが表示されて、正しく値が取得できていることが確認できました。
次は逆方向、つまりサーバからクライアントサイドの呼び出しを行ってみます。今回はタイマーを使って 10 秒ごとにクライアントサイドの呼び出しを行います。
まずはクライアントサイドから実装します。
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript" src="Scripts/jquery-1.6.4.js"></script> <script type="text/javascript" src="Scripts/jquery.signalR.js"></script> <script type="text/javascript" src="signalR/hubs"></script> <script type="text/javascript"> $(function () { var sample = $.connection.sample; sample.tick = function (value) { alert(value); }; }); </script> </head> <body> </body> </html>
変更点は sample.tick を追加したぐらいです。sample にメソッドを追加すると、Hub からの呼び出しが可能になります。
[HubName("sample")] public class SampleHub : Hub { public SampleHub() { _timer = new Timer(state => { Clients.tick("はうはう - " + DateTime.Now); }, null, 0, 10000); } private Timer _timer; }
そして Hub の実装です。Timer クラスを使って 10 秒にコールバックされるようにしました。クライアントサイドで tick メソッドを定義しているので、コールバック内でも tick メソッドを呼び出すコードを書いています。
注目すべきは Clients プロパティで、これは dynamic になっています。Clients に対して tick をメソッドとして呼び出すと、接続中の全てのクライアントに対して tick メソッドの呼び出しが行わるというわけです。
分かりにくい図にしてみるとこんな対応関係です。
実際に実行してみました。メッセージと現在時刻が表示されているので、サーバからクライアントへのメソッド呼び出しが行われていることが確認できますね。
ちなみに Clients は全ての接続中クライアントに対して呼び出しが行われますが、メソッドを呼び出したクライアントのみにコールバックを行いたい場合には Caller プロパティを使うと実現できます。
他にも ConnectionId を使って特定のクライアントのみに呼び出しを行う方法もありますが、それはまた今度。
まとめると SignalR を使うと、サーバ側とクライアント側を非常にシームレスに扱うことが出来るので、WebSockets や XHR を使ったときのように接続の管理など、API のエンドポイントを全く考える必要がないのが素晴らしいですね。
バージョン 0.4 と IIS 8.0 の組み合わせでは WebSockets をバックエンドで使うことが出来るので、リアルタイム通信ライブラリの決定版じゃないかと勝手に思っています。