SignalR的實時高頻通訊
第五章SignalR的實時高頻通訊
概述:本例子演示了如果創建一個對象與其他瀏覽器共享實時狀態的應用程序。我們要創建的應用程序為“MoveShape”,該MoveShape頁面會顯示一個Html Div元素,用戶可以拖動,並且在用戶拖動時,該元素的新位置將被發送到服務器,這樣,其他所有已連接的客戶端都會同步更新該元素的位置。
在這個例子中使用的應用程序是基於迪米安·愛德華茲的Demo制作的,你可以在這里看到該視頻。
本例子將演示從形狀的拖動事件引發時,如何發送SignalR消息開始,至每個已連接的客戶端將收到該消息,並且更新本地形狀的位置。
雖然使用這種方法能夠很好地對SignalR的高頻實時功能進行演示,但這不是一個推薦的編程模型。因為:
1)沒有限制發送消息上限,會令客戶端和服務器端發送和接收大量的消息,最終導致性能 下降。
2)每次接收到新位置后,形狀的位置會由方法立即更新,而不是平滑地移動到新位置,所 以客戶端上形狀動畫也會被打亂。
本例子的后面部分將演示如何創建一個定時器的功能,限制該消息在客戶端和服務器端之間的發送更新消息的最大頻率。本例子創建的應用程序的最終版可以在這里下載。
1.創建項目並添加SignalR和jQuery.UI NuGet包:
1)在VS2013中創建一個Asp.Net Web應用程序:
2)在新的Asp.Net項目窗口中,選擇空項目並且創建:
3)在解決方案資源管理器中,右擊該項目,添加一個SignalR集顯器類(V2),該類命名為MoveShapeHub.cs,並將其添加到項目中,此步驟創建MoveShapeHub類,並將SignalR腳本和程序集引用添加到該項目中。
注意:您同樣可以用庫軟件包管理器來添加SignalR引用,可以參考前面的例子。
4)使用庫軟件包管理器來添加jQueryUI。
在程序包管理控制台中,運行以下命令:
Install-Package jQuery.UI.Combined |
該步驟將jQuery.UI庫添加到項目中。
5)在解決方案資源管理器中展開腳本文件夾,您可以看到SignalR和jQuery腳本已經被添加到項目中:
2.創建基礎應用程序:
在本節中,我們將創建在客戶端中鼠標移動事件觸發時,形狀的位置發送到服務器的應用程序。至於創建服務器廣播該消息給所有其他已連接的客戶端的功能,我們將在后面的章節中繼續講解,現在,請把注意力集中在集線器類的創建上。
1)如果在之前您使用包控制台來添加SignalR,請先添加MoveShapeHub類到項目中。
2)使用下面的代碼替代換掉的MoveShapeHub中的:
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; } } } |
MoveShapeHub是SignalR集線器類的一個實現。在入門教程中,我們使用了客戶端直接調用的方法。在這本教程中,客戶端將會發送一個包含形 狀的新的X及Y坐標點對象到服務器上,並且被廣播給其他所有已連接的客戶端。SignalR將使用JSON來序列化該對象。
我們創建了一個ShapeModel類來作為坐標屬性的容器,它包含了形狀位置的信息。同時,我們需要指定那些客戶僅作為消息的接收端。所以服務器上 的對象還包含一個成員跟蹤那些客戶端的數據被儲存。這樣,指定的客戶端才不會發送它自己的形狀坐標數據到服務器上。該成員使用JsonIgnore屬性, 防止它被序列化並被發送到客戶端。
3.在應用程序啟動時啟用集線器:
1) 我們將把設置在應用程序啟動時,自動啟用集線器映射。在SignalR2.0中,這是通過增加OWIN啟動類來實現的。啟動類的配置方法中會調用MapSigalR方法,同時啟動類會使用Assembly特性來將啟動類注冊到OWIN的啟動處理過程中。
在解決方案資源管理器中,添加一個OWIN啟動類,將其命名為Startup並添加。
2)使用一下的代碼替換Startup類的內容:
using Microsoft.Owin; using Owin;
[assembly: OwinStartup(typeof(MoveShapeDemo.Startup))] namespace MoveShapeDemo { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR(); } } } |
4.添加客戶端:
1)接下來,我們將添加客戶端。添加一個Html頁面,並且命名為Default.html帶項目中。
2)在解決方案資源管理器中,右擊剛剛添加的頁面,點擊設為起始頁。
3)用下面的代碼替換Html頁面中的:
<!DOCTYPE html> <html> <head> <title>SignalR MoveShape Demo</title> <style>#shape { width: 100px;height: 100px;}</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.0.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> |
意:請檢查代碼中所引用的腳本是否同腳本文件夾中的一致:
上面的Html和JS代碼創建了一個紅色的Div,id為Shape。在Shape拖動時,將觸發它的drag事件,並將Div的位置發送給服務器。
4) 按下F5啟動應用程序,復制頁面的URL並打開一個新的瀏覽器,粘貼並打開,拖動一個瀏覽器的窗口中的形狀,另一個瀏覽器的形狀位置也將同步進行更新。
5.添加服務器循環:
在目前的應用中,每當服務器接收到新的消息時,都會將它們廣播到所有的客戶端上。同客戶端的問題一樣:消息總是不斷發送,而不是在需要時才發送,並且可能導致連接被結果淹沒。以下服務器代碼就是為了解決這一問題:已實現節流出書消息的速率定時器(減少並發,防止不斷並發而導致連接的實例沒創建,就要發送,導致發送失敗,也可以防止並發連接和並發推送消息太多,導致同一時間資源占用過多,)。
1)使用以下代碼更新MoveShapeHub:
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; } } } |
上面的代碼新增了Broadcaster類,它使用.Net框架中的Timer類來對發送消息進行節流。
由於集線器本身是暫時存在的(每次都是需要時才創建),Broadcaster被創建為一個單例。使用率延遲初始化(.Net4中新增功能),來推遲其創建時間,直到需要它為止。這是為了確保在計時器開始之前,就有集線器的實例被成功創建完畢。
調用客戶端的updateShape功能,被移出集線器的updateModel方法,所有消息傳入后,它將不再立即被調用。相反,需要發送至客戶端的消息會已每秒25次的頻率進行並發。Broadcaster類中的_broadcastLoop計時器來承擔發送頻率的管理功能。最終,集線器並不直接調用客戶端方法,Broadcaster類需要使用GlobalHost來獲得一個引用當前正在運行的操作集線器(_hubContext)。
2)按F5啟動應用程序,復制窗口並拖動,將不會同上一節中的效果有太大區別。但在后台,我們已經對發送到客戶端的消息進行了節流限制。
6.為客戶端加入流暢的動畫效果:
這個應用程序到這里,已經很完善了,但我們還能有進一步的改進。客戶端的形狀移動是由接受到服務器的消息而進行的。我們將使用jQueryUI庫的animate功能來優化形狀的移動效果,而不是直接使用服務器提供的新位置來直接改變形狀的當前位置。
1)使用下面代碼更新Html頁面:
<!DOCTYPE html> <html> <head> <title>SignalR MoveShape Demo</title> <style> |
|
#shape { width: 100px; height: 100px; } </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.0.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> |
上面的代碼將使用動畫來把形狀移動到新的位置上,在本例子中,我們使用100毫秒作為動畫間隔。
2)按下F5啟動應用程序,復制窗口並且拖動,您可以看到形狀的移動比之前更流暢,形狀每次移動是隨着時間進行補插而不是每當有新消息傳入就立即更新一次。
7.下一步:
在本例子中,您學習了如何編寫客戶端同服務器端高頻實時通訊的SignalR應用,這種通信模式被經常用來開發網絡游戲。比如:the ShootR game created with SignalR。