サーバーから、クライアントへの通知を行いたくて、SignalR を調査してみた。サンプルは、JavaScript と、.NET サーバーのものが多かったので、ASP.NET MVC + .NET Client という構成で簡単なサンプルを作ってみた。通知をしたいなら、ポーリングでもいいし、Notification Hub でもいいはず。でもそれらのメリット・デメリットを理解したいので、まず、SignalR をやってみることとにした。
SignalR とは
SignalR はリアルタイムの双方向通信を簡単におこなうライブラリで、例えばチャットや、対戦ゲーム、プログレスバーみたいな用途に使えるようです。Microsoft で使っているような言語のライブラリが用意されています。問題はWebの情報が古いので動くかドキドキすることですが、意外とあっさり動いてくれました。手順は、VS2012 とかの手順ですが、同じ感じでVS2017でも動作しました。
SignalR の良いところは、双方向通信と聞くと、「Websocketじゃね?」と思うのですが、WebSocketが使えないケースでもServer Sent Events, Forever Frame, Ajax long polling など、Comet ベースのトランスポートもサポートしてくれています。それをライブラリが吸収してくれているらしいです。
アーキテクチャ
下記のようになっていて、サーバー側からクライアントのコードを、クライアントからサーバーのコードを、Hub/HubProxyを通じて双方向に呼べるようになっています。これが隠蔽されていて簡単にコードで書けるのでいい感じですね。
HTML5 ベース、そして、Cometベースのトランスポートに対応しています。
ちなみに、現時点で NuGetパッケージを検索すると、Angular をはじめ、いろいろなクライアントがサポートされていました。サンプルは、JQueryのサポートだったので、つらかったw。(Javaもあるみたいです)。
今回は、クライアント・サーバー両方 .NET での例で実施してみます。
サーバーのコード
に従えば、ASP.NET MVC + javascript はできてしまうので、違うパターンで。
サーバーはとても簡単で、ASP.NET MVC のテンプレートを使います。(使わなくてもいいです)クラスを追加してみると、SignalR のテンプレートがすでに存在します。
これをプロジェクトを選択して追加します。
次にちょいとエディットします。
ChatHub.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
namespace SignalRSamples
{
public class ChatHub : Hub
{
public void Send(string name, string message)
{
Clients.All.addNewMessageToPage(name, message);
}
}
}
このメソッドがクライアントから呼ばれたら、すべてのクライアントに対して、addNewMessageToPageというメッセージを投げるというプログラムです。そこに引数を持っています。
ちなみに、このテンプレートを使うと、こっそり、Reference と、 Javascript のスクリプトに、SignalRのライブラリが追加されていることがわかります。
これだけでは、だめなので、Startup.cs クラスを作成します。これもテンプレートで作れます。
Startup.cs
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(SignalRSamples.Startup))]
namespace SignalRSamples
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
app.MapSignalR();
}
}
}
スタートアップ時に、ここで、Application Builder パイプラインに、SignalR を追加して有効化します。ここでデフォルトで、SignalRのエンドポイントは、/singalr
になります。これを変更したい場合はこちら参照のこと。変更したい場合は、このapp.MapSignalR()
に引数を持たせることができます。
このあたりのStartupがどういうものか?とかの構造に関してはここに解説があるみたい。また読んでみよう。(未読)
そして、デバッグモードで起動する。ちなみに、最初に何故かArgumentOutOfRangeExceptionが出た。
StackOverflow だと、IISをいれるといいよだったけど、VS2017 を Administratorモードで動かしたら動くようになった。
こういう直感的ではないエラーはつらいなー。でもなんとかなった。これでまずはサーバー終わり。
クライアント
クライアントは簡単だ。簡単のため、コンソールアプリで作ってみた。NuGetでMicrosoft.AspNet.SignalR.Client
をインストールしています。それだけ。
Program.cs
using Microsoft.AspNet.SignalR.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SignalRClient
{
class Program
{
static void Main(string[] args)
{
MainAsync(args).GetAwaiter().GetResult();
}
static async Task MainAsync(string[] args)
{
Console.Write("What is your name? : ");
var yourname = Console.ReadLine();
var hubConnection = new HubConnection("http://localhost:1218/signalr"); // 1. Hub へのコネクション
IHubProxy chatProxy = hubConnection.CreateHubProxy("ChatHub"); // 2. Proxy の作成
chatProxy.On("addNewMessageToPage", (string name, string message) => // 3. サーバーからのコールバック設定
{
Console.WriteLine($"{name} : {message}");
Console.Write("Message :");
});
await hubConnection.Start(); // 4. コネクションの開始
Console.Write("Message :");
while (true)
{
var yourmessage = Console.ReadLine();
await chatProxy.Invoke("Send", yourname, yourmessage); // 4. サーバーのメソッド呼び出し
}
}
}
}
見た目のとおりなのですが、先ほど作成したサーバーへのコネクションを設定します。
var hubConnection = new HubConnection("http://localhost:1218/signalr"); // 1. Hub へのコネクション
つぎにProxyの作成。Hubの名前はサーバー側で作ったものに合わせます。
IHubProxy chatProxy = hubConnection.CreateHubProxy("ChatHub"); // 2. Proxy の作成
Proxyに対して、Onメソッドで、サーバーからのコールバックを設定します。
chatProxy.On("addNewMessageToPage", (string name, string message) => // 3. サーバーからのコールバック設定
{
Console.WriteLine($"{name} : {message}");
Console.Write("Message :");
});
先ほどのサーバー側のコードをもう一度見てみましょう。サーバー側からクライアントを読んでいるコードが、Client.All.addNewMessageToPage
で、クライアント側のコールバックと引数含めて一致しています。これが呼ばれたら、クライアントのコールバックが呼ばれます。
public void Send(string name, string message)
{
Clients.All.addNewMessageToPage(name, message);
}
次にサーバー側のメソッド呼び出しです。チャットなので何回もメッセージ送りたいので、ループさせてみました。
Console.Write("Message :");
while (true)
{
var yourmessage = Console.ReadLine();
await chatProxy.Invoke("Send", yourname, yourmessage); // 4. サーバーのメソッド呼び出し
}
サーバー側の、Send
メソッドをプロキシ経由で呼び出しているのがわかると思います。これで双方向通信をよきに計らってくれるってこら簡単だわ!
実行。冒頭のと同じですが、1台のサーバーをあげて、2台のクライアントをあげてチャットしています。
片方のコンソールでメッセージをうつと、別の方にも即座に反映されます。こらちょっと楽しいなw
ちなみみに、今回のソースコードGitHubに上げています。
次は、Notification Hub とか、これをサーバーレスの環境でどう活用するかとかを楽しんでみたいと思います。ちなみに、私は個人的には、Javascript(JQuery) + ASP.NET MVC のチュートもやってみたのですが、同じぐらい簡単でした。これは、Angular でも簡単なんだろうなぁ。