SignalR 2.0 系列:SignalR的高頻實時通訊

這是微軟官方SignalR 2.0教程Getting Started with ASP.NET SignalR 2.0系列的翻譯,這裏是第七篇:SignalR的高頻實時通訊

原文:Tutorial: High-Frequency Realtime with SignalR 2.0

概述

本教程演示如何創建一個對象與其他瀏覽器共享實時狀態的應用程序。我們要穿件的應用程序爲MoveShape,該MoveShape頁面會顯示一個Html Div元素,用戶可以拖動。並且在用戶拖動時,該元素的新位置被髮送到服務器,這樣其他所有已連接的客戶端都會同步更新該元素的位置。

\

這個教程中使用的應用程序是基於迪米安·愛德華茲的Demo製作的,你可以在這裏看到該視頻。

本教程將演示從形狀的拖動事件引發時如何發送SignalR消息開始,至每個已連接的客戶端將接收該消息並更新本地形狀的位置。

雖然使用這種方法能夠很好的對SignalR的功能進行演示,但這不是一個推薦的編程模型。因爲沒有限制發送消息的上限,所以客戶端和服務器會發送與接收大量的消息,最終導致性能的下降。同時客戶端上形狀的動畫也會被打亂,因爲每次接收到位置後形狀的位置會由方法立即更新,而不是平滑的移動到新位置。本教程的後面部分將演示如何創建一個定時器功能,限制該消息在客戶端和服務器之間發送更新消息的最大頻率。本教程中創建的應用程序的最終版本可以在這裏下載。

創建項目並添加SignalR和jQuery.UI NuGet包

在本節中,我們使用VS2013來創建項目。

下面演示使用VS2013來創建一個空的ASP.NET應用程序項目,並添加SignalR和jQuery庫:

1.在VS2013中創建一個ASP.NET WEB應用程序。

\

2.在新的ASP.NET項目窗口中,選擇空項目並創建。

\

3.在解決方案資源管理器中,右擊該項目,添加一個SignalR集線器類(V2),將該類命名爲MoveShapeHub.cs並將其添加到項目中,此步驟創建MoveShapeHub類,並將SignalR腳本和程序集引用添加到該項目中。

\

注意:您同樣可以用庫軟件包管理器來添加SignalR引用,可以參見前面的教程。

4.使用庫軟件包管理器來添加jQueryUI。

在程序包管理器控制檯中,運行以下命令:

1.Install-Package jQuery.UI.Combined

\

該步將jQuery.UI庫添加到項目中。

5.在解決方案資源管理器中展開腳本文件夾,你可以看到SignalR和jQuery腳本已經被添加到項目中。

\

創建基礎應用程序

在本節中,我們將創建在客戶端中鼠標移動事件觸發時將形狀的位置發送到服務器的應用程序。至於創建服務器廣播該消息給所有其它已連接的客戶端的功能,我們將在後面的章節繼續。 現在把注意力集中在集線器類的創建上。

1.如果在之前您使用包控制檯來添加SignalR,請先添加MoveShapeHub類到項目中。

2.使用下面的代碼替換掉MoveShapeHub中的:

01.using Microsoft.AspNet.SignalR;
02.using Newtonsoft.Json;
03. 
04.namespace MoveShapeDemo
05.{
06.public class MoveShapeHub : Hub
07.{
08.public void UpdateModel(ShapeModel clientModel)
09.{
10.clientModel.LastUpdatedBy = Context.ConnectionId;
11.// Update the shape model within our broadcaster
12.Clients.AllExcept(clientModel.LastUpdatedBy).updateShape(clientModel);
13.}
14.}
15.public class ShapeModel
16.{
17.// We declare Left and Top as lowercase with
18.// JsonProperty to sync the client and server models
19.[JsonProperty("left")]
20.public double Left { getset; }
21.[JsonProperty("top")]
22.public double Top { getset; }
23.// We don't want the client to get the "LastUpdatedBy" property
24.[JsonIgnore]
25.public string LastUpdatedBy { getset; }
26.}
27.}

MoveShapeHub是SignalR集線器類的一個實現。在入門教程中,我們使用了客戶端直接調用的方法。在這本教程中,客戶端將會發送一個包含形狀的新的X及Y座標點對象到服務器上,並且被廣播給其他所有已連接的客戶端。SignalR將使用JSON來序列化該對象。

我們創建了一個ShapeModel類來作爲座標屬性的容器,它包含了形狀位置的信息。同時,我們需要指定那些客戶單僅僅作爲消息的接收端。所以服務器上的對象還包含一個成員跟蹤那些客戶端的數據被儲存。這樣,指定的客戶端纔不會發送它自己的形狀座標數據到服務器上。該成員使用JsonIgnore屬性,防止它被序列化並被髮送到客戶端。

在應用程序啓動時啓用集線器

1.我們將把設置在應用程序啓動時,自動啓用集線器映射。在SignalR 2.0中,這是通過增加OWIN啓動類來實現的。啓動類在類的配置方法中會調用MapSignalR方法,同時啓動類會使用Assembly特性來將啓動類註冊到OWIN的啓動處理過程中。

在解決方案資源管理器中,添加一個OWIN啓動類,將其命名爲Startup並添加。

\

2.使用以下的代碼替換Startup類的內容:

01.using Microsoft.Owin;
02.using Owin;
03. 
04.[assembly: OwinStartup(typeof(MoveShapeDemo.Startup))]
05.namespace MoveShapeDemo
06.{
07.public class Startup
08.{
09.public void Configuration(IAppBuilder app)
10.{
11.// Any connection or hub wire up and configuration should go here
12.app.MapSignalR();
13.}
14.}
15.}

添加客戶端

1.接下來,我們將添加客戶端。添加一個HTML頁面並命名爲Default.html到項目中。

2.在解決方案資源管理其中,右擊剛剛添加的頁面,點擊設爲起始頁。

3.用下面的代碼替換HTML頁面中的:

01.<!DOCTYPE html>
02.<html>
03.<head>
04.<title>SignalR MoveShape Demo</title>
05.<style>
06.#shape {
07.width: 100px;
08.height: 100px;
09.background-color: #FF0000;
10.}
11.</style>
12.</head>
13.<body>
14.<script src="Scripts/jquery-1.10.2.min.js"></script>
15.<script src="Scripts/jquery-ui-1.10.4.min.js"></script>
16.<script src="Scripts/jquery.signalR-2.0.0.js"></script>
17.<script src="/signalr/hubs"></script>
18.<script>
19.$(function () {
20.var moveShapeHub = $.connection.moveShapeHub,
21.$shape = $("#shape"),
22.shapeModel = {
23.left: 0,
24.top: 0
25.};
26.moveShapeHub.client.updateShape = function (model) {
27.shapeModel = model;
28.$shape.css({ left: model.left, top: model.top });
29.};
30.$.connection.hub.start().done(function () {
31.$shape.draggable({
32.drag: function () {
33.shapeModel = $shape.offset();
34.moveShapeHub.server.updateModel(shapeModel);
35.}
36.});
37.});
38.});
39.</script>
40. 
41.<div id="shape" />
42.</body>
43.</html>

注意:請檢查代碼中所引用的腳本是否同腳本文件夾中的一致:
\

上面的HTML和JS代碼創建了一個紅色的Div,id爲Shape。在Shape拖動時,將觸發它的drag事件,並將Div的位置發送給服務器。

4.按下F5啓動應用程序,複製頁面的URL並打開一個新的瀏覽器,粘貼並打開,拖動一個瀏覽器的窗口中的形狀,另一個瀏覽器的形狀位置也將同步進行更新。

 \

添加客戶端循環

由於每一次移動鼠標都會發送位置信息到服務器端並進行廣播,這將大大影響網絡流量及程序的性能。我們需要對客戶端的消息進行節流限制。我們將使用JS的setIntrval函數來設置一個固定速度的循環方法,使用該方法以固定的頻率將形狀的位置信息發送到服務器。這個循環是一個'遊戲循環',一個被反覆調用的函數,用於驅動所有需要定期檢查或其他模擬功能的方法。

1.用以下代碼更新HTML頁的內容:

01.<!DOCTYPE html>
02.<html>
03.<head>
04.<title>SignalR MoveShape Demo</title>
05.<style>
06.#shape {
07.width: 100px;
08.height: 100px;
09.background-color: #FF0000;
10.}
11.</style>
12.</head>
13.<body>
14.<script src="Scripts/jquery-1.10.2.min.js"></script>
15.<script src="Scripts/jquery-ui-1.10.4.min.js"></script>
16.<script src="Scripts/jquery.signalR-2.0.1.js"></script>
17.<script src="/signalr/hubs"></script>
18.<script>
19.$(function () {
20.var moveShapeHub = $.connection.moveShapeHub,
21.$shape = $("#shape"),
22.// Send a maximum of 10 messages per second
23.// (mouse movements trigger a lot of messages)
24.messageFrequency = 10,
25.// Determine how often to send messages in
26.// time to abide by the messageFrequency
27.updateRate = 1000 / messageFrequency,
28.shapeModel = {
29.left: 0,
30.top: 0
31.},
32.moved = false;
33.moveShapeHub.client.updateShape = function (model) {
34.shapeModel = model;
35.$shape.css({ left: model.left, top: model.top });
36.};
37.$.connection.hub.start().done(function () {
38.$shape.draggable({
39.drag: function () {
40.shapeModel = $shape.offset();
41.moved = true;
42.}
43.});
44.// Start the client side server update interval
45.setInterval(updateServerModel, updateRate);
46.});
47.function updateServerModel() {
48.// Only update server if we have a new movement
49.if (moved) {
50.moveShapeHub.server.updateModel(shapeModel);
51.moved = false;
52.}
53.}
54.});
55.</script>
56. 
57.<div id="shape" />
58.</body>
59.</html>

我們創建了updateServerModel方法來使用一個固定頻率將位置信息發送給服務器。當move標誌位變動時,該函數纔將信息傳送給服務器。

2.按下F5運行,同樣複製一個瀏覽器窗口,拖動一個窗口中的形狀並觀察另一個。這一次我們發送的數據將被節流,所以你可以看到動畫將不如之前的那樣平滑。

\

添加服務器循環

在目前的應用中,每當服務器接收到新消息時,都會將它們廣播到所有客戶端上。同客戶端的問題一樣:消息總是發送而不是在需要時才發送,並且連接可能被結果淹沒。本節介紹如何更新服務器代碼以實現節流傳出消息的速率定時器。

1.使用以下代碼更新MoveShapeHub:

01.using System;
02.using System.Threading;
03.using Microsoft.AspNet.SignalR;
04.using Newtonsoft.Json;
05. 
06.namespace MoveShapeDemo
07.{
08.public class Broadcaster
09.{
10.private readonly static Lazy<Broadcaster> _instance =
11.new Lazy<Broadcaster>(() => new Broadcaster());
12.// We're going to broadcast to all clients a maximum of 25 times per second
13.private readonly TimeSpan BroadcastInterval =
14.TimeSpan.FromMilliseconds(40);
15.private readonly IHubContext _hubContext;
16.private Timer _broadcastLoop;
17.private ShapeModel _model;
18.private bool _modelUpdated;
19.public Broadcaster()
20.{
21.// Save our hub context so we can easily use it
22.// to send to its connected clients
23._hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
24._model = new ShapeModel();
25._modelUpdated = false;
26.// Start the broadcast loop
27._broadcastLoop = new Timer(
28.BroadcastShape,
29.null,
30.BroadcastInterval,
31.BroadcastInterval);
32.}
33.public void BroadcastShape(object state)
34.{
35.// No need to send anything if our model hasn't changed
36.if (_modelUpdated)
37.{
38.// This is how we can access the Clients property
39.// in a static hub method or outside of the hub entirely
40._hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
41._modelUpdated = false;
42.}
43.}
44.public void UpdateShape(ShapeModel clientModel)
45.{
46._model = clientModel;
47._modelUpdated = true;
48.}
49.public static Broadcaster Instance
50.{
51.get
52.{
53.return _instance.Value;
54.}
55.}
56.}
57. 
58.public class MoveShapeHub : Hub
59.{
60.// Is set via the constructor on each creation
61.private Broadcaster _broadcaster;
62.public MoveShapeHub()
63.this(Broadcaster.Instance)
64.{
65.}
66.public MoveShapeHub(Broadcaster broadcaster)
67.{
68._broadcaster = broadcaster;
69.}
70.public void UpdateModel(ShapeModel clientModel)
71.{
72.clientModel.LastUpdatedBy = Context.ConnectionId;
73.// Update the shape model within our broadcaster
74._broadcaster.UpdateShape(clientModel);
75.}
76.}
77.public class ShapeModel
78.{
79.// We declare Left and Top as lowercase with
80.// JsonProperty to sync the client and server models
81.[JsonProperty("left")]
82.public double Left { getset; }
83.[JsonProperty("top")]
84.public double Top { getset; }
85.// We don't want the client to get the "LastUpdatedBy" property
86.[JsonIgnore]
87.public string LastUpdatedBy { getset; }
88.}
89. 
90.}

上面的代碼新增了Broadcaster類,它使用.Net框架中的Timer類來對發送消息進行節流。

由於集線器本身是暫時存在的(每次需要時才創建),Broadcaster被創建爲一個單例。使用了延遲初始化(.Net4中新增功能),來推遲其創建時間直到需要它爲止。這是爲了確保在計時器開始之前就有集線器的實例被成功創建完畢。

調用客戶端的updateShape功能被移出集線器的updateModel方法,所有消息傳入後它將不再立即被調用。相反,需要發送至客戶端的消息會以每秒25次的頻率進行發送。Broadcaster類中的_broadcastLoop計時器來承擔發送頻率的管理功能。

最終,集線器並不直接調用客戶端方法,Broadcaster類需要使用GlobalHost來獲得一個引用當前正在運行的操作集線器(_hubContext)。

2.按F5啓動應用程序,複製窗口並拖動,將不會同上一節中的效果有太大差別。但在後臺,我們已經對發送到客戶端的消息進行了節流限制。

\

爲客戶端加入流暢的動畫效果

這個應用程序已經很完善了,但我們還需要做進一步的改進。客戶端的形狀移動是由接收到服務器消息而進行的,我們將使用jQuery UI庫的animate功能來優化形狀的移動效果,而不是直接使用服務器提供的新位置來改變形狀的當前位置。

1.使用下面的代碼更新HTML頁面:

01.<!DOCTYPE html>
02.<html>
03.<head>
04.<title>SignalR MoveShape Demo</title>
05.<style>
06.#shape {
07.width: 100px;
08.height: 100px;
09.background-color: #FF0000;
10.}
11.</style>
12.</head>
13.<body>
14.<script src="Scripts/jquery-1.10.2.min.js"></script>
15.<script src="Scripts/jquery-ui-1.10.4.min.js"></script>
16.<script src="Scripts/jquery.signalR-2.0.0.js"></script>
17.<script src="/signalr/hubs"></script>
18.<script>
19.$(function () {
20.var moveShapeHub = $.connection.moveShapeHub,
21.$shape = $("#shape"),
22.// Send a maximum of 10 messages per second
23.// (mouse movements trigger a lot of messages)
24.messageFrequency = 10,
25.// Determine how often to send messages in
26.// time to abide by the messageFrequency
27.updateRate = 1000 / messageFrequency,
28.shapeModel = {
29.left: 0,
30.top: 0
31.},
32.moved = false;
33.moveShapeHub.client.updateShape = function (model) {
34.shapeModel = model;
35.// Gradually move the shape towards the new location (interpolate)
36.// The updateRate is used as the duration because by the time
37.// we get to the next location we want to be at the "last" location
38.// We also clear the animation queue so that we start a new
39.// animation and don't lag behind.
40.$shape.animate(shapeModel, { duration: updateRate, queue: false });
41.};
42.$.connection.hub.start().done(function () {
43.$shape.draggable({
44.drag: function () {
45.shapeModel = $shape.offset();
46.moved = true;
47.}
48.});
49.// Start the client side server update interval
50.setInterval(updateServerModel, updateRate);
51.});
52.function updateServerModel() {
53.// Only update server if we have a new movement
54.if (moved) {
55.moveShapeHub.server.updateModel(shapeModel);
56.moved = false;
57.}
58.}
59.});
60.</script>
61. 
62.<div id="shape" />
63.</body>
64.</html>

上面的代碼將使用動畫來把形狀移動到新的位置上,在此例中,我們使用100毫秒作爲動畫間隔。

2.按下F5啓動應用程序,複製窗口並拖動,你可以看到形狀的移動比之前更流暢。形狀每次移動是隨着時間進行插補而不是每當有消息傳入就立即更新一次。

\

下一步

在本教程中,您學習瞭如何編寫客戶端同服務器端高頻實時通訊的SignalR應用,這種通信模式被經常用來開發網絡遊戲。比如:the ShootR game created with SignalR。

作者:<strong -webkit-text-stroke-width:="" background-color:="" font-size:="" font-style:="" font-variant:="" segoe="" vertical-align:="" white-space:="" word-spacing:="">帕特里克·弗萊徹 -帕特里克·弗萊徹是ASP.NET開發團隊的程序員,作家,目前正在SignalR項目工作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章