SignalR

SignalR でChat アプリを作ってみた

サーバーから、クライアントへの通知を行いたくて、SignalR を調査してみた。サンプルは、JavaScript と、.NET サーバーのものが多かったので、ASP.NET MVC + .NET Client という構成で簡単なサンプルを作ってみた。通知をしたいなら、ポーリングでもいいし、Notification Hub でもいいはず。でもそれらのメリット・デメリットを理解したいので、まず、SignalR をやってみることとにした。

SignalR.png

SignalR とは

SignalR はリアルタイムの双方向通信を簡単におこなうライブラリで、例えばチャットや、対戦ゲーム、プログレスバーみたいな用途に使えるようです。Microsoft で使っているような言語のライブラリが用意されています。問題はWebの情報が古いので動くかドキドキすることですが、意外とあっさり動いてくれました。手順は、VS2012 とかの手順ですが、同じ感じでVS2017でも動作しました。

SignalR の良いところは、双方向通信と聞くと、「Websocketじゃね?」と思うのですが、WebSocketが使えないケースでもServer Sent Events, Forever Frame, Ajax long polling など、Comet ベースのトランスポートもサポートしてくれています。それをライブラリが吸収してくれているらしいです。

アーキテクチャ

下記のようになっていて、サーバー側からクライアントのコードを、クライアントからサーバーのコードを、Hub/HubProxyを通じて双方向に呼べるようになっています。これが隠蔽されていて簡単にコードで書けるのでいい感じですね。

image1.png

HTML5 ベース、そして、Cometベースのトランスポートに対応しています。

image5.png

ちなみに、現時点で NuGetパッケージを検索すると、Angular をはじめ、いろいろなクライアントがサポートされていました。サンプルは、JQueryのサポートだったので、つらかったw。(Javaもあるみたいです)。

今回は、クライアント・サーバー両方 .NET での例で実施してみます。

サーバーのコード

に従えば、ASP.NET MVC + javascript はできてしまうので、違うパターンで。

サーバーはとても簡単で、ASP.NET MVC のテンプレートを使います。(使わなくてもいいです)クラスを追加してみると、SignalR のテンプレートがすでに存在します。

Hub.png

これをプロジェクトを選択して追加します。

次にちょいとエディットします。

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のライブラリが追加されていることがわかります。

reference.png

jquery.png

これだけでは、だめなので、Startup.cs クラスを作成します。これもテンプレートで作れます。

Startup.png

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が出た。

Error.png

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台のクライアントをあげてチャットしています。

SignalR.png

片方のコンソールでメッセージをうつと、別の方にも即座に反映されます。こらちょっと楽しいなw

ちなみみに、今回のソースコードGitHubに上げています。

次は、Notification Hub とか、これをサーバーレスの環境でどう活用するかとかを楽しんでみたいと思います。ちなみに、私は個人的には、Javascript(JQuery) + ASP.NET MVC のチュートもやってみたのですが、同じぐらい簡単でした。これは、Angular でも簡単なんだろうなぁ。

Resources