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 をドラッグすると、その新しい位置がサーバーに送信され、接続されている他のすべてのクライアントに対して、シェイプの位置を一致するように更新することが指示されます。
このチュートリアルで作成したアプリケーションは、Damian Edwards によるデモに基づいています。 このデモを含む動画は、ここで見ることができます。
このチュートリアルでは、まずシェイプのドラッグ時に発生する各イベントから SignalR メッセージを送信する方法を示します。 次に、接続された各クライアントは、メッセージを受信するたびに、シェイプのローカル バージョンの位置を更新します。
アプリケーションはこのメソッドを使っても機能しますが、これは推奨されるプログラミング モデルではありません。送信されるメッセージ数に上限がないため、クライアントとサーバーがメッセージに対処できなくなり、パフォーマンスが低下する可能性があります。 また、クライアント上に表示されるアニメーションはバラバラになります。シェイプがそれぞれの新しい位置にスムーズに移動するのではなく、各メソッドによって即時に移動されるからです。 このチュートリアルで後述するセクションでは、クライアントまたはサーバーのいずれかから送信されるメッセージの最大レートを制限するタイマー関数を作成する方法と、位置間でシェイプをスムーズに移動する方法を示します。 このチュートリアルで作成したアプリケーションの最終バージョンは、コード ギャラリーからダウンロードできます。
このトピックは、次のセクションで構成されています。
- 前提条件
- プロジェクトを作成する
- ASP.NET SignalR と JQuery.UI NuGet パッケージを追加する
- 基本アプリケーションを作成する
- クライアント ループを追加する
- サーバー ループを追加する
- クライアントにスムーズ アニメーションを追加する
- その他の手順
前提条件
このチュートリアルには 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 でプロジェクトを作成します。
[ファイル] メニューの [新しいプロジェクト] をクリックします。
[新しいプロジェクト] ダイアログ ボックスで [テンプレート] の下の [C#] を展開し、[Web] を選びます。
[ASP.NET 空の Web アプリケーション] テンプレートを選び、プロジェクトに MoveShapeDemo という名前を付けて、[OK] をクリックします。
SignalR および JQuery.UI NuGet パッケージを追加する
NuGet パッケージをインストールすることで、SignalR 機能をプロジェクトに追加できます。 このチュートリアルでは、シェイプをドラッグしてアニメーション化できるようにするために JQuery.UI パッケージも使います。
[ツール] | [NuGet パッケージ マネージャー] | [パッケージ マネージャー コンソール] をクリックします。
パッケージ マネージャーで次のコマンドを入力します。
Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
SignalR パッケージを使うと、他の多数の NuGet パッケージを依存関係としてインストールすることができます。 インストールが完了すると、ASP.NET アプリケーションで SignalR を使うために必要なサーバーとクライアントのコンポーネントがすべて揃います。
パッケージ マネージャー コンソールに次のコマンドを入力して、JQuery と JQuery.UI のパッケージをインストールします。
Install-Package jQuery.ui.combined
基本アプリケーションを作成する
このセクションでは、各マウス移動イベント中にシェイプの位置をサーバーに送信するブラウザー アプリケーションを作成します。 次に、サーバーはこの情報を受信すると、接続されている他のすべてのクライアントにこの情報をブロードキャストします。 このアプリケーションについては、後のセクションで詳しく説明します。
ソリューション エクスプローラーでプロジェクトを右クリックし、[追加]、[クラス] を選びます。クラスに「MoveShapeHub」という名前を付け、[追加] をクリックします。
新しい 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
属性を使います。次に、アプリケーションの起動時にハブを設定します。 ソリューション エクスプローラーでプロジェクトを右クリックし、[追加] | [グローバル アプリケーション クラス] をクリックします。 既定の名前「Global」をそのまま使い、[OK] をクリックします。
Global.asax.cs クラスで、指定された using ステートメントの後に次の
using
ステートメントを追加します。using System.Web.Routing;
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(); } } }
次に、クライアントを追加します。 ソリューション エクスプローラーでプロジェクトを右クリックし、[追加] | [新しい項目] をクリックします。 [新しい項目の追加] ダイアログで、[HTML ページ] を選びます。 ページに適切な名前 (Default.html など) を付け、[追加] をクリックします。
ソリューション エクスプローラーで、先ほど作成したページを右クリックし、[スタート ページに設定] をクリックします。
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
イベントを使ってシェイプの位置がサーバーに送信されます。F5 キーを押してアプリケーションを起動します。 ページの URL をコピーし、2 つ目のブラウザー ウィンドウに貼り付けます。 いずれかのブラウザー ウィンドウでシェイプをドラッグします。他のブラウザー ウィンドウのシェイプが移動するはずです。
クライアント ループを追加する
マウス移動イベントごとにシェイプの位置を送信すると、不必要な量のネットワーク トラフィックが発生するため、クライアントからのメッセージを調整する必要があります。 JavaScript の setInterval
関数を使って、新しい位置情報を固定レートでサーバーに送信するループを設定します。 このループは、"ゲーム ループ" の非常に基本的な表現であり、ゲームやその他のシミュレーションのすべての機能を推進する、繰り返し呼び出される関数です。
次のコード スニペットと一致するように、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
フラグが送信する新しい位置データがあることを示すたびに、位置データをサーバーに送信します。F5 キーを押してアプリケーションを起動します。 ページの URL をコピーし、2 つ目のブラウザー ウィンドウに貼り付けます。 いずれかのブラウザー ウィンドウでシェイプをドラッグします。他のブラウザー ウィンドウのシェイプが移動するはずです。 サーバーに送信されるメッセージ数が調整されるため、アニメーションは前のセクションほどスムーズには表示されません。
サーバー ループを追加する
現在のアプリケーションでは、サーバーからクライアントに送信されるメッセージは、受信と同じ頻度で送信されます。 これにより、クライアントで発生したものと同様の問題が発生します。メッセージは必要以上の頻度で送信される可能性があり、その結果、接続フラッドが発生する可能性があります。 このセクションでは、送信メッセージのレートを抑えるタイマーを実装するようにサーバーを更新する方法について説明します。
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
) への参照を取得する必要があります。F5 キーを押してアプリケーションを起動します。 ページの URL をコピーし、2 つ目のブラウザー ウィンドウに貼り付けます。 いずれかのブラウザー ウィンドウでシェイプをドラッグします。他のブラウザー ウィンドウのシェイプが移動するはずです。 ブラウザーには前のセクションとの明らかな違いはありませんが、クライアントに送信されるメッセージ数は調整されます。
クライアントにスムーズ アニメーションを追加する
アプリケーションはほぼ完成しましたが、サーバー メッセージに応じてクライアント上でシェイプが移動する動作について、もう 1 つ改善の余地があります。 シェイプの位置をサーバーによって指定された新しい位置に設定するのではなく、JQuery UI ライブラリの animate
関数を使って、現在の位置と新しい位置の間でシェイプをスムーズに移動します。
クライアントの
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 ミリ秒) の間に、シェイプを古い位置からサーバーによって指定された新しい位置に移動します。 シェイプ上で実行されている以前のアニメーションは、新しいアニメーションが開始される前にクリアされます。
F5 キーを押してアプリケーションを起動します。 ページの URL をコピーし、2 つ目のブラウザー ウィンドウに貼り付けます。 いずれかのブラウザー ウィンドウでシェイプをドラッグします。他のブラウザー ウィンドウのシェイプが移動するはずです。 他のウィンドウ内のシェイプの動きは、受信メッセージごとに 1 回設定されるのではなく、時間の経過と共に補間されるため、動きのぎこちなさが軽減されるはずです。
その他の手順
このチュートリアルでは、クライアントとサーバー間で高頻度のメッセージを送信する SignalR アプリケーションをプログラムする方法について説明しました。 このコミュニケーション パラダイムは、SignalR で作成された ShootR ゲームのようなオンライン ゲームやその他のシミュレーションを開発するのに役立ちます。
このチュートリアルで作成し、完成したアプリケーションは、コード ギャラリーからダウンロードできます。
SignalR の開発概念の詳細については、SignalR のソース コードとリソースに関する次のサイトを参照してください。