チュートリアル: SignalR 2 を使用して高頻度リアルタイム アプリを作成する

このチュートリアルでは、ASP.NET SignalR 2 を使用して高頻度メッセージング機能を提供する Web アプリケーションを作成する方法について説明します。 この場合、"高頻度メッセージング" とは、サーバーが固定レートで更新を送信することを意味します。 1 秒間に最大 10 個のメッセージを送信します。

作成するアプリケーションには、ユーザーがドラッグできる図形が表示されます。 サーバーは、時間更新を使用して、ドラッグされた図形の位置と一致するように、接続されているすべてのブラウザーの図形の位置を更新します。

このチュートリアルで紹介する概念には、リアルタイム ゲームのアプリケーションやその他のシミュレーション アプリケーションがあります。

このチュートリアルでは、次の作業を行いました。

  • プロジェクトのセットアップ
  • 基本アプリケーションを作成する
  • アプリの起動時にハブにマップする
  • クライアントを追加する
  • アプリを実行する
  • クライアント ループを追加する
  • サーバー ループを追加する
  • スムーズ アニメーションを追加する

警告

このドキュメントは、最新版の SignalR を対象としていません。 ASP.NET Core SignalR に関する記事を参照してください。

前提条件

プロジェクトのセットアップ

このセクションでは、Visual Studio 2017 でプロジェクトを作成します。

このセクションでは、Visual Studio 2017 を使用して空の ASP.NET Web アプリケーションを作成し、SignalR と jQuery.UI のライブラリを追加する方法について説明します。

  1. Visual Studio で、ASP.NET Web アプリケーションを作成します。

    Create web

  2. [新しい ASP.NET Web アプリケーション - MoveShapeDemo] ウィンドウで、[空] を選択したまま [OK] を選択します。

  3. ソリューション エクスプローラーで、プロジェクトを右クリックして、[追加]>[新しい項目] を選択します。

  4. [新しい項目の追加 - MoveShapeDemo] で、[インストール済み]>[Visual C#]>[Web]>[SignalR] を選択して、[SignalR Hub クラス (v2)] を選択します。

  5. クラスに「MoveShapeHub」という名前を付け、プロジェクトに追加します。

    この手順では、MoveShapeHub.cs クラス ファイルを作成します。 同時に、SignalR をサポートするスクリプト ファイルとアセンブリ参照のセットをプロジェクトに追加します。

  6. [ツール]>[NuGet パッケージ マネージャー]>[パッケージ マネージャー コンソール] の順に選択します。

  7. パッケージ マネージャー コンソールで、次のコマンドを実行します。

    Install-Package jQuery.UI.Combined
    

    このコマンドを実行すると、jQuery UI ライブラリがインストールされます。 これを使用して、図形をアニメーション化します。

  8. ソリューション エクスプローラーで [スクリプト] ノードを展開します。

    Script library references

    jQuery、jQueryUI、SignalR のスクリプト ライブラリがプロジェクトに表示されます。

基本アプリケーションを作成する

このセクションでは、ブラウザー アプリケーションを作成します。 このアプリは、各マウス移動イベント中に図形の場所をサーバーに送信します。 サーバーは、この情報を他のすべての接続済みクライアントにリアルタイムでブロードキャストします。 このアプリケーションの詳細については、後のセクションで説明します。

  1. MoveShapeHub.cs ファイルを開きます。

  2. MoveShapeHub.cs ファイル内のコードを次のコードに置き換えます。

    using Microsoft.AspNet.SignalR;
    using Newtonsoft.Json;
    
    namespace MoveShapeDemo
    {
        public class MoveShapeHub : Hub
        {
            public void UpdateModel(ShapeModel clientModel)
            {
                clientModel.LastUpdatedBy = Context.ConnectionId;
                // Update the shape model within our broadcaster
                Clients.AllExcept(clientModel.LastUpdatedBy).updateShape(clientModel);
            }
        }
        public class ShapeModel
        {
            // We declare Left and Top as lowercase with 
            // JsonProperty to sync the client and server models
            [JsonProperty("left")]
            public double Left { get; set; }
            [JsonProperty("top")]
            public double Top { get; set; }
            // We don't want the client to get the "LastUpdatedBy" property
            [JsonIgnore]
            public string LastUpdatedBy { get; set; }
        }
    }
    
  3. ファイルを保存します。

MoveShapeHub クラスは、SignalRハブの実装です。 SignalR の概要チュートリアルと同様に、ハブにはクライアントが直接呼び出すメソッドがあります。 この場合、クライアントは図形の新しい X 座標と Y 座標を持つオブジェクトをサーバーに送信します。 これらの座標は、他のすべての接続されたクライアントにブロードキャストされます。 SignalR は、JSON を使用してこのオブジェクトを自動的にシリアル化します。

アプリは ShapeModel オブジェクトをクライアントに送信します。 これには、図形の位置を保存するメンバーがあります。 サーバー上のオブジェクトのバージョンにも、どのクライアントのデータが保存されているかを追跡するメンバーがあります。 このオブジェクトは、サーバーがクライアントのデータをそれ自体に送信することを防ぎます。 このメンバーは、JsonIgnore 属性を使用して、アプリケーションがデータをシリアル化してクライアントに送り返すのを防ぎます。

アプリの起動時にハブにマップする

次に、アプリケーション起動時のハブへのマッピングを設定します。 SignalR 2 では、OWIN スタートアップ クラスを追加するとマッピングが作成されます。

  1. ソリューション エクスプローラーで、プロジェクトを右クリックして、[追加]>[新しい項目] を選択します。

  2. [新しい項目の追加 - MoveShapeDemo] で、[インストール済み]>[Visual C#]>[Web] を選択して、[OWIN スタートアップ クラス] を選択します。

  3. クラスに "Startup" という名前を付けて、[OK] を選択します。

  4. Startup.cs ファイル内の既定のコードを次のコードに置き換えます。

    using Microsoft.Owin;
    using Owin;
    
    [assembly: OwinStartup(typeof(MoveShapeDemo.Startup))]
    namespace MoveShapeDemo
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                // Any connection or hub wire up and configuration should go here
                app.MapSignalR();
            }
        }
    }
    

OWIN スタートアップ クラスは、アプリが Configuration メソッドを実行したときに MapSignalR を呼び出します。 アプリは、OwinStartup アセンブリ属性を使用して OWIN のスタートアップ プロセスにクラスを追加します。

クライアントを追加する

クライアントの HTML ページを追加します。

  1. ソリューション エクスプローラーで、プロジェクトを右クリックして、[追加]>[HTML ページ] を選択します。

  2. ページに「Default」という名前を付け、[OK] を選択します。

  3. ソリューション エクスプローラ[Default.html] を右クリックし、[スタート ページに設定] を選択します。

  4. Default.html ファイル内の既定のコードを次のコードに置き換えます。

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    </head>
    <body>
    <script src="Scripts/jquery-1.10.2.min.js"></script>
    <script src="Scripts/jquery-ui-1.10.4.min.js"></script>
    <script src="Scripts/jquery.signalR-2.1.0.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
     $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                $shape = $("#shape"),
                shapeModel = {
                    left: 0,
                    top: 0
                };
                moveShapeHub.client.updateShape = function (model) {
                    shapeModel = model;
                    $shape.css({ left: model.left, top: model.top });
                };
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moveShapeHub.server.updateModel(shapeModel);
                        }
                    });
                });
            });
    </script>
        
        <div id="shape" />
    </body>
    </html>
    
  5. ソリューション エクスプローラーで、[スクリプト] を展開します。

    jQuery と SignalR のスクリプト ライブラリがプロジェクトに表示されます。

    重要

    パッケージ マネージャーにより、SignalR スクリプトの新しいバージョンがインストールされます。

  6. コード ブロック内のスクリプト参照を、プロジェクト内のスクリプト ファイルのバージョンに対応するように更新します。

この HTML と JavaScript コードでは、shape という名前の赤い div が作成されます。 これは jQuery ライブラリを使用して図形のドラッグ動作を有効にし、drag イベントを使用して図形の位置をサーバーに送信します。

アプリを実行する

アプリを実行して動作することを確認できます。 ブラウザー ウィンドウで図形をドラッグすると、他のブラウザーでも図形が移動します。

  1. ツールバーの [スクリプト デバッグ] をオンにして、再生ボタンを選択し、アプリケーションをデバッグ モードで実行します。

    Screenshot of user turning on debugging mode and selecting play.

    ブラウザー ウィンドウが開き、右上隅に赤い図形が表示されます。

  2. ページの URL をコピーします。

  3. 別のブラウザー インスタンスを開き、アドレス バーに URL を貼り付けます。

  4. いずれかのブラウザー ウィンドウで図形をドラッグします。 他のブラウザー ウィンドウの図形がそれに続きます。

アプリケーションはこのメソッドを使用して機能していますが、これは推奨されるプログラミング モデルではありません。 送信されるメッセージ数に上限がありません。 その結果、クライアントとサーバーはメッセージに圧倒され、パフォーマンスが低下します。 また、アプリはクライアントにちぐはぐなアニメーションを表示します。 このぎくしゃくしたアニメーションが表示されるのは、各メソッドによって図形が瞬時に移動するためです。 図形がそれぞれの新しい場所にスムーズに移動する方が望ましいです。 次に、これらの問題を解決する方法について説明します。

クライアント ループを追加する

マウスの移動イベントごとに図形の場所を送信すると、不要な量のネットワーク トラフィックが作成されます。 アプリは、クライアントからのメッセージを抑える必要があります。

javascript setInterval 関数を使用して、新しい位置情報を固定レートでサーバーに送信するループを設定します。 このループは、"ゲーム ループ" の基本的な表現です。これは、ゲームのすべての機能を駆動する、繰り返し呼び出される関数です。

  1. Default.html ファイル内のクライアント コードを次のコードに置き換えます。

    <!DOCTYPE html>
    <html>
    <head>
    <title>SignalR MoveShape Demo</title>
    <style>
        #shape {
            width: 100px;
            height: 100px;
            background-color: #FF0000;
        }
    </style>
    </head>
    <body>
    <script src="Scripts/jquery-1.10.2.min.js"></script>
    <script src="Scripts/jquery-ui-1.10.4.min.js"></script>
    <script src="Scripts/jquery.signalR-2.1.0.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
        $(function () {
            var moveShapeHub = $.connection.moveShapeHub,
                $shape = $("#shape"),
                // Send a maximum of 10 messages per second 
                // (mouse movements trigger a lot of messages)
                messageFrequency = 10, 
                // Determine how often to send messages in
                // time to abide by the messageFrequency
                updateRate = 1000 / messageFrequency, 
                shapeModel = {
                    left: 0,
                    top: 0
                },
                moved = false;
            moveShapeHub.client.updateShape = function (model) {
                shapeModel = model;
                $shape.css({ left: model.left, top: model.top });
            };
            $.connection.hub.start().done(function () {
                $shape.draggable({
                    drag: function () {
                        shapeModel = $shape.offset();
                        moved = true;
                    }
                });
                // Start the client side server update interval
                setInterval(updateServerModel, updateRate);
            });
            function updateServerModel() {
                // Only update server if we have a new movement
                if (moved) {
                    moveShapeHub.server.updateModel(shapeModel);
                    moved = false;
                }
            }
        });
    </script>
       
    <div id="shape" />
    </body>
    </html>
    

    重要

    スクリプト参照をもう一度置き換える必要があります。 これらは、プロジェクト内のスクリプトのバージョンと一致する必要があります。

    この新しいコードでは、updateServerModel 関数が追加されます。 これは、固定の頻度で呼び出されます。 この関数は、moved フラグが送信する新しい位置データがあることを示すたびに、位置データをサーバーに送信します。

  2. 再生ボタンを選択してアプリケーションを起動します。

  3. ページの URL をコピーします。

  4. 別のブラウザー インスタンスを開き、アドレス バーに URL を貼り付けます。

  5. いずれかのブラウザー ウィンドウで図形をドラッグします。 他のブラウザー ウィンドウの図形がそれに続きます。

アプリはサーバーに送信されるメッセージ数を抑えるため、アニメーションは最初のようにスムーズに表示されません。

サーバー ループを追加する

現在のアプリケーションでは、サーバーからクライアントに送信されるメッセージは、受信と同じ頻度で送信されます。 このネットワーク トラフィックにより、クライアントで確認したように、同様の問題が示されます。

アプリは、必要以上に頻繁にメッセージを送信できます。 その結果、接続がフラッディングされる可能性があります。 このセクションでは、送信メッセージのレートを抑えるタイマーを追加するようにサーバーを更新する方法について説明します。

  1. MoveShapeHub.cs の内容を次のコードに置き換えます。

    using System;
    using System.Threading;
    using Microsoft.AspNet.SignalR;
    using Newtonsoft.Json;
    
    namespace MoveShapeDemo
    {
        public class Broadcaster
        {
            private readonly static Lazy<Broadcaster> _instance = 
                new Lazy<Broadcaster>(() => new Broadcaster());
            // We're going to broadcast to all clients a maximum of 25 times per second
            private readonly TimeSpan BroadcastInterval = 
                TimeSpan.FromMilliseconds(40); 
            private readonly IHubContext _hubContext;
            private Timer _broadcastLoop;
            private ShapeModel _model;
            private bool _modelUpdated;
            public Broadcaster()
            {
                // Save our hub context so we can easily use it 
                // to send to its connected clients
                _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
                _model = new ShapeModel();
                _modelUpdated = false;
                // Start the broadcast loop
                _broadcastLoop = new Timer(
                    BroadcastShape, 
                    null, 
                    BroadcastInterval, 
                    BroadcastInterval);
            }
            public void BroadcastShape(object state)
            {
                // No need to send anything if our model hasn't changed
                if (_modelUpdated)
                {
                    // This is how we can access the Clients property 
                    // in a static hub method or outside of the hub entirely
                    _hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
                    _modelUpdated = false;
                }
            }
            public void UpdateShape(ShapeModel clientModel)
            {
                _model = clientModel;
                _modelUpdated = true;
            }
            public static Broadcaster Instance
            {
                get
                {
                    return _instance.Value;
                }
            }
        }
            
        public class MoveShapeHub : Hub
        {
            // Is set via the constructor on each creation
            private Broadcaster _broadcaster;
            public MoveShapeHub()
                : this(Broadcaster.Instance)
            {
            }
            public MoveShapeHub(Broadcaster broadcaster)
            {
                _broadcaster = broadcaster;
            }
            public void UpdateModel(ShapeModel clientModel)
            {
                clientModel.LastUpdatedBy = Context.ConnectionId;
                // Update the shape model within our broadcaster
                _broadcaster.UpdateShape(clientModel);
            }
        }
        public class ShapeModel
        {
            // We declare Left and Top as lowercase with 
            // JsonProperty to sync the client and server models
            [JsonProperty("left")]
            public double Left { get; set; }
            [JsonProperty("top")]
            public double Top { get; set; }
            // We don't want the client to get the "LastUpdatedBy" property
            [JsonIgnore]
            public string LastUpdatedBy { get; set; }
        }
        
    }
    
  2. 再生ボタンを選択してアプリケーションを起動します。

  3. ページの URL をコピーします。

  4. 別のブラウザー インスタンスを開き、アドレス バーに URL を貼り付けます。

  5. いずれかのブラウザー ウィンドウで図形をドラッグします。

このコードは、クライアントを拡張して Broadcaster クラスを追加します。 新しいクラスは、.NET Framework のTimer クラスを使用して送信メッセージを抑えます。

ハブ自体が一時的であることも好都合です。 これは、必要なたびに作成されます。 そのため、アプリは Broadcaster をシングルトンとして作成します。 遅延初期化を使用して、必要になるまで Broadcaster の作成を延期します。 これにより、タイマーを開始する前に、アプリによって最初のハブ インスタンスが完全に作成されます。

その後、クライアントの UpdateShape 関数の呼び出しがハブの UpdateModel メソッドから移動されます。 アプリが受信メッセージを受信するたびにすぐに呼び出されることはなくなります。 代わりに、アプリは 1 秒あたり 25 回の呼び出しレートでクライアントにメッセージを送信します。 このプロセスは、Broadcaster クラス内から _broadcastLoop タイマーによって管理されます。

最後に、ハブからクライアント メソッドを直接呼び出す代わりに、Broadcaster クラスは現在動作している _hubContext ハブへの参照を取得する必要があります。 GlobalHost を使用して参照を取得します。

スムーズ アニメーションを追加する

アプリケーションはほぼ完成していますが、もう 1 つ改善できます。 アプリは、サーバー メッセージに応答してクライアント上の図形を移動します。 図形の位置をサーバーによって指定された新しい場所に設定する代わりに、JQuery UI ライブラリの animate 関数を使用します。 これにより、現在の位置と新しい位置の間で図形をスムーズに移動できます。

  1. Default.html ファイル内のクライアントの updateShape メソッドを、強調表示されているコードのように更新します。

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    </head>
    <body>
    <script src="Scripts/jquery-1.10.2.min.js"></script>
    <script src="Scripts/jquery-ui-1.10.4.min.js"></script>
    <script src="Scripts/jquery.signalR-2.1.0.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
            $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                    $shape = $("#shape"),
                    // Send a maximum of 10 messages per second 
                    // (mouse movements trigger a lot of messages)
                    messageFrequency = 10, 
                    // Determine how often to send messages in
                    // time to abide by the messageFrequency
                    updateRate = 1000 / messageFrequency, 
                    shapeModel = {
                        left: 0,
                        top: 0
                    },
                    moved = false;
                moveShapeHub.client.updateShape = function (model) {
                     shapeModel = model;
                     // Gradually move the shape towards the new location (interpolate)
                     // The updateRate is used as the duration because by the time 
                     // we get to the next location we want to be at the "last" location
                     // We also clear the animation queue so that we start a new 
                     // animation and don't lag behind.
                     $shape.animate(shapeModel, { duration: updateRate, queue: false });
                };
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moved = true;
                        }
                    });
                    // Start the client side server update interval
                    setInterval(updateServerModel, updateRate);
                });
                function updateServerModel() {
                    // Only update server if we have a new movement
                    if (moved) {
                        moveShapeHub.server.updateModel(shapeModel);
                        moved = false;
                    }
                }
            });
    </script>
       
        <div id="shape" />
    </body>
    </html>
    
  2. 再生ボタンを選択してアプリケーションを起動します。

  3. ページの URL をコピーします。

  4. 別のブラウザー インスタンスを開き、アドレス バーに URL を貼り付けます。

  5. いずれかのブラウザー ウィンドウで図形をドラッグします。

もう一方のウィンドウの図形の動きが、あまりぎくしゃくしなくなります。 アプリは、受信メッセージごとに 1 回移動を設定するのではなく、時間の経過に伴って移動を補間します。

このコードは、図形を古い場所から新しい場所に移動します。 サーバーは、アニメーション間隔の過程で図形の位置を指定します。 この場合、これは 100 ミリ秒です。 アプリは、新しいアニメーションが開始される前に、図形で実行されていた以前のアニメーションをすべてクリアします。

コードを取得する

完成したプロジェクトのダウンロード

その他のリソース

ここで説明したコミュニケーション パラダイムは、SignalR で作成された ShootR ゲームのようなオンライン ゲームやその他のシミュレーションを開発するのに役立ちます。

SignalR の詳細については、次のリソースを参照してください。

次のステップ

このチュートリアルでは、次の作業を行いました。

  • プロジェクトのセットアップ
  • 基本アプリケーションを作成しました
  • アプリの起動時にハブにマップしました
  • クライアントを追加しました
  • アプリを実行しました
  • クライアント ループを追加しました
  • サーバー ループを追加しました
  • スムーズ アニメーションを追加しました

次の記事では、ASP.NET SignalR 2 を使用してサーバー ブロードキャスト機能を提供する Web アプリケーションを作成する方法について説明します。