次の方法で共有


SignalR 1.x による高頻度リアルタイム

作成者: Patrick Fletcher

警告

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

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

このチュートリアルで作成するアプリケーションには、ユーザーがドラッグできるシェイプが表示されます。 接続されている他のすべてのブラウザー内のシェイプの位置は、時間が指定された更新を使って、ドラッグされたシェイプの位置と一致するように更新されます。

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

このチュートリアルに関するコメントをぜひお寄せください。 チュートリアルに直接関連しない質問がある場合は、ASP.NET SignalR フォーラムまたは StackOverflow.com に投稿できます。

概要

このチュートリアルでは、オブジェクトの状態を他のブラウザーとリアル タイムで共有するアプリケーションを作成する方法について説明します。 これから作成するアプリケーションの名前は MoveShape です。 MoveShape ページには、ユーザーがドラッグできる HTML Div 要素が表示されます。ユーザーが Div をドラッグすると、その新しい位置がサーバーに送信され、接続されている他のすべてのクライアントに対して、シェイプの位置を一致するように更新することが指示されます。

Screenshot showing the MoveShape application page.

このチュートリアルで作成したアプリケーションは、Damian Edwards によるデモに基づいています。 このデモを含む動画は、ここで見ることができます。

このチュートリアルでは、まずシェイプのドラッグ時に発生する各イベントから SignalR メッセージを送信する方法を示します。 次に、接続された各クライアントは、メッセージを受信するたびに、シェイプのローカル バージョンの位置を更新します。

アプリケーションはこのメソッドを使っても機能しますが、これは推奨されるプログラミング モデルではありません。送信されるメッセージ数に上限がないため、クライアントとサーバーがメッセージに対処できなくなり、パフォーマンスが低下する可能性があります。 また、クライアント上に表示されるアニメーションはバラバラになります。シェイプがそれぞれの新しい位置にスムーズに移動するのではなく、各メソッドによって即時に移動されるからです。 このチュートリアルで後述するセクションでは、クライアントまたはサーバーのいずれかから送信されるメッセージの最大レートを制限するタイマー関数を作成する方法と、位置間でシェイプをスムーズに移動する方法を示します。 このチュートリアルで作成したアプリケーションの最終バージョンは、コード ギャラリーからダウンロードできます。

このトピックは、次のセクションで構成されています。

前提条件

このチュートリアルには Visual Studio 2012 または Visual Studio 2010 が必要です。 Visual Studio 2010 を使う場合、プロジェクトには .NET Framework 4.5 ではなく .NET Framework 4 が使われます。

Visual Studio 2012 を使っている場合は、ASP.NET and Web Tools 2012.2 更新プログラムをインストールすることをお勧めします。 この更新プログラムには、発行の機能強化、新しい機能、新しいテンプレートなどの新機能が含まれています。

Visual Studio 2010 を使っている場合は、NuGet がインストールされていることを確認します。

プロジェクトを作成する

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

  1. [ファイル] メニューの [新しいプロジェクト] をクリックします。

  2. [新しいプロジェクト] ダイアログ ボックスで [テンプレート] の下の [C#] を展開し、[Web] を選びます。

  3. [ASP.NET 空の Web アプリケーション] テンプレートを選び、プロジェクトに MoveShapeDemo という名前を付けて、[OK] をクリックします。

    Creating the new project

SignalR および JQuery.UI NuGet パッケージを追加する

NuGet パッケージをインストールすることで、SignalR 機能をプロジェクトに追加できます。 このチュートリアルでは、シェイプをドラッグしてアニメーション化できるようにするために JQuery.UI パッケージも使います。

  1. [ツール] | [NuGet パッケージ マネージャー] | [パッケージ マネージャー コンソール] をクリックします。

  2. パッケージ マネージャーで次のコマンドを入力します。

    Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
    

    SignalR パッケージを使うと、他の多数の NuGet パッケージを依存関係としてインストールすることができます。 インストールが完了すると、ASP.NET アプリケーションで SignalR を使うために必要なサーバーとクライアントのコンポーネントがすべて揃います。

  3. パッケージ マネージャー コンソールに次のコマンドを入力して、JQuery と JQuery.UI のパッケージをインストールします。

    Install-Package jQuery.ui.combined
    

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

このセクションでは、各マウス移動イベント中にシェイプの位置をサーバーに送信するブラウザー アプリケーションを作成します。 次に、サーバーはこの情報を受信すると、接続されている他のすべてのクライアントにこの情報をブロードキャストします。 このアプリケーションについては、後のセクションで詳しく説明します。

  1. ソリューション エクスプローラーでプロジェクトを右クリックし、[追加][クラス] を選びます。クラスに「MoveShapeHub」という名前を付け、[追加] をクリックします。

  2. 新しい MoveShapeHub クラスのコードを次のコードに置き換えます。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    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; }
        }
    }
    

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

    クライアント (ShapeModel) に送信されるオブジェクトには、シェイプの位置を格納するメンバーが含まれています。 サーバー上のオブジェクトのバージョンには、どのクライアントのデータが格納されているかを追跡するメンバーも含まれているため、特定のクライアントにそれ自身のデータが送信されることはありません。 このメンバーは、シリアル化されてクライアントに送信されないように、JsonIgnore 属性を使います。

  3. 次に、アプリケーションの起動時にハブを設定します。 ソリューション エクスプローラーでプロジェクトを右クリックし、[追加] | [グローバル アプリケーション クラス] をクリックします。 既定の名前「Global」をそのまま使い、[OK] をクリックします。

    Add Global Application Class

  4. Global.asax.cs クラスで、指定された using ステートメントの後に次の using ステートメントを追加します。

    using System.Web.Routing;
    
  5. Global クラスの Application_Start メソッドに次のコード行を追加して、SignalR の既定のルートを登録します。

    RouteTable.Routes.MapHubs();
    

    global.asax ファイルは次のようになります。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Security;
    using System.Web.SessionState;
    
    using System.Web.Routing;
    
    namespace MoveShapeDemo
    {
        public class Global : System.Web.HttpApplication
        {
            protected void Application_Start(object sender, EventArgs e)
            {
                RouteTable.Routes.MapHubs();
            }
        }
    }
    
  6. 次に、クライアントを追加します。 ソリューション エクスプローラーでプロジェクトを右クリックし、[追加] | [新しい項目] をクリックします。 [新しい項目の追加] ダイアログで、[HTML ページ] を選びます。 ページに適切な名前 (Default.html など) を付け、[追加] をクリックします。

  7. ソリューション エクスプローラーで、先ほど作成したページを右クリックし、[スタート ページに設定] をクリックします。

  8. HTML ページの既定のコードを次のコード スニペットに置き換えます。

    Note

    次のスクリプト参照が、Scripts フォルダー内のプロジェクトに追加されたパッケージと一致することを確認します。 Visual Studio 2010 では、プロジェクトに追加された JQuery と SignalR のバージョンが以前のバージョン番号と一致しない場合があります。

    <!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.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.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>
    

    上記の HTML と JavaScript のコードにより、Shape という赤い Div が作成され、jQuery ライブラリを使ってシェイプのドラッグ動作が有効になり、シェイプの drag イベントを使ってシェイプの位置がサーバーに送信されます。

  9. F5 キーを押してアプリケーションを起動します。 ページの URL をコピーし、2 つ目のブラウザー ウィンドウに貼り付けます。 いずれかのブラウザー ウィンドウでシェイプをドラッグします。他のブラウザー ウィンドウのシェイプが移動するはずです。

    Screenshot showing how a shape you drag in one browser window moves in another window.

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

マウス移動イベントごとにシェイプの位置を送信すると、不必要な量のネットワーク トラフィックが発生するため、クライアントからのメッセージを調整する必要があります。 JavaScript の setInterval 関数を使って、新しい位置情報を固定レートでサーバーに送信するループを設定します。 このループは、"ゲーム ループ" の非常に基本的な表現であり、ゲームやその他のシミュレーションのすべての機能を推進する、繰り返し呼び出される関数です。

  1. 次のコード スニペットと一致するように、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.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.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. F5 キーを押してアプリケーションを起動します。 ページの URL をコピーし、2 つ目のブラウザー ウィンドウに貼り付けます。 いずれかのブラウザー ウィンドウでシェイプをドラッグします。他のブラウザー ウィンドウのシェイプが移動するはずです。 サーバーに送信されるメッセージ数が調整されるため、アニメーションは前のセクションほどスムーズには表示されません。

    Screenshot showing how a shape you drag in one browser window moves in another window when you add a client loop.

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

現在のアプリケーションでは、サーバーからクライアントに送信されるメッセージは、受信と同じ頻度で送信されます。 これにより、クライアントで発生したものと同様の問題が発生します。メッセージは必要以上の頻度で送信される可能性があり、その結果、接続フラッドが発生する可能性があります。 このセクションでは、送信メッセージのレートを抑えるタイマーを実装するようにサーバーを更新する方法について説明します。

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

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    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; }
        }
        
    }
    

    上記のコードでは、クライアントを拡張して Broadcaster クラスを追加しています。ここで .NET Framework の Timer クラスを使って送信メッセージを調整します。

    ハブ自体は一時的なものであるため (必要になるたびに作成されます)、Broadcaster はシングルトンとして作成されます。 遅延初期化 (.NET 4 で導入されました) を使って、必要になるまで作成を延期し、タイマーが開始される前に最初のハブ インスタンスが完全に作成されるようにします。

    その後、クライアントの UpdateShape 関数の呼び出しはハブの UpdateModel メソッドの外に移動されるため、受信メッセージを受信するたびに即時に呼び出されなくなります。 代わりに、クライアントへのメッセージは、Broadcaster クラス内の _broadcastLoop タイマーによって管理され、1 秒あたり 25 回の呼び出し頻度で送信されます。

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

  2. F5 キーを押してアプリケーションを起動します。 ページの URL をコピーし、2 つ目のブラウザー ウィンドウに貼り付けます。 いずれかのブラウザー ウィンドウでシェイプをドラッグします。他のブラウザー ウィンドウのシェイプが移動するはずです。 ブラウザーには前のセクションとの明らかな違いはありませんが、クライアントに送信されるメッセージ数は調整されます。

    Screenshot showing how a shape you drag in one browser window moves in another window when you add a server loop.

クライアントにスムーズ アニメーションを追加する

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

  1. クライアントの 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.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.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>
    

    上記のコードでは、アニメーション間隔 (この場合は 100 ミリ秒) の間に、シェイプを古い位置からサーバーによって指定された新しい位置に移動します。 シェイプ上で実行されている以前のアニメーションは、新しいアニメーションが開始される前にクリアされます。

  2. F5 キーを押してアプリケーションを起動します。 ページの URL をコピーし、2 つ目のブラウザー ウィンドウに貼り付けます。 いずれかのブラウザー ウィンドウでシェイプをドラッグします。他のブラウザー ウィンドウのシェイプが移動するはずです。 他のウィンドウ内のシェイプの動きは、受信メッセージごとに 1 回設定されるのではなく、時間の経過と共に補間されるため、動きのぎこちなさが軽減されるはずです。

    Screenshot showing how a shape you drag in one browser window moves in another window when you add smooth animation on the client.

その他の手順

このチュートリアルでは、クライアントとサーバー間で高頻度のメッセージを送信する SignalR アプリケーションをプログラムする方法について説明しました。 このコミュニケーション パラダイムは、SignalR で作成された ShootR ゲームのようなオンライン ゲームやその他のシミュレーションを開発するのに役立ちます。

このチュートリアルで作成し、完成したアプリケーションは、コード ギャラリーからダウンロードできます。

SignalR の開発概念の詳細については、SignalR のソース コードとリソースに関する次のサイトを参照してください。