英文渣水平,大伙湊合着看吧……
這是微軟官方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。
在程序包管理器控制台中,運行以下命令:
Install-Package jQuery.UI.Combined
該步將jQuery.UI庫添加到項目中。
5.在解決方案資源管理器中展開腳本文件夾,你可以看到SignalR和jQuery腳本已經被添加到項目中。
創建基礎應用程序
在本節中,我們將創建在客戶端中鼠標移動事件觸發時將形狀的位置發送到服務器的應用程序。至於創建服務器廣播該消息給所有其它已連接的客戶端的功能,我們將在后面的章節繼續。 現在把注意力集中在集線器類的創建上。
1.如果在之前您使用包控制台來添加SignalR,請先添加MoveShapeHub類到項目中。
2.使用下面的代碼替換掉MoveShapeHub中的:
1 using Microsoft.AspNet.SignalR; 2 using Newtonsoft.Json; 3 4 namespace MoveShapeDemo 5 { 6 public class MoveShapeHub : Hub 7 { 8 public void UpdateModel(ShapeModel clientModel) 9 { 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 { get; set; } 21 [JsonProperty("top")] 22 public double Top { get; set; } 23 // We don't want the client to get the "LastUpdatedBy" property 24 [JsonIgnore] 25 public string LastUpdatedBy { get; set; } 26 } 27 }
MoveShapeHub是SignalR集線器類的一個實現。在入門教程中,我們使用了客戶端直接調用的方法。在這本教程中,客戶端將會發送一個包含形狀的新的X及Y坐標點對象到服務器上,並且被廣播給其他所有已連接的客戶端。SignalR將使用JSON來序列化該對象。
我們創建了一個ShapeModel類來作為坐標屬性的容器,它包含了形狀位置的信息。同時,我們需要指定那些客戶單僅僅作為消息的接收端。所以服務器上的對象還包含一個成員跟蹤那些客戶端的數據被儲存。這樣,指定的客戶端才不會發送它自己的形狀坐標數據到服務器上。該成員使用JsonIgnore屬性,防止它被序列化並被發送到客戶端。
在應用程序啟動時啟用集線器
1.我們將把設置在應用程序啟動時,自動啟用集線器映射。在SignalR 2.0中,這是通過增加OWIN啟動類來實現的。啟動類在類的配置方法中會調用MapSignalR方法,同時啟動類會使用Assembly特性來將啟動類注冊到OWIN的啟動處理過程中。
在解決方案資源管理器中,添加一個OWIN啟動類,將其命名為Startup並添加。
2.使用以下的代碼替換Startup類的內容:
1 using Microsoft.Owin; 2 using Owin; 3 4 [assembly: OwinStartup(typeof(MoveShapeDemo.Startup))] 5 namespace MoveShapeDemo 6 { 7 public class Startup 8 { 9 public void Configuration(IAppBuilder app) 10 { 11 // Any connection or hub wire up and configuration should go here 12 app.MapSignalR(); 13 } 14 } 15 } 16
添加客戶端
1.接下來,我們將添加客戶端。添加一個HTML頁面並命名為Default.html到項目中。
2.在解決方案資源管理其中,右擊剛剛添加的頁面,點擊設為起始頁。
3.用下面的代碼替換HTML頁面中的:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>SignalR MoveShape Demo</title> 5 <style> 6 #shape { 7 width: 100px; 8 height: 100px; 9 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頁的內容:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>SignalR MoveShape Demo</title> 5 <style> 6 #shape { 7 width: 100px; 8 height: 100px; 9 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:
1 using System; 2 using System.Threading; 3 using Microsoft.AspNet.SignalR; 4 using Newtonsoft.Json; 5 6 namespace MoveShapeDemo 7 { 8 public class Broadcaster 9 { 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 { get; set; } 83 [JsonProperty("top")] 84 public double Top { get; set; } 85 // We don't want the client to get the "LastUpdatedBy" property 86 [JsonIgnore] 87 public string LastUpdatedBy { get; set; } 88 } 89 90 }
上面的代碼新增了Broadcaster類,它使用.Net框架中的Timer類來對發送消息進行節流。
由於集線器本身是暫時存在的(每次需要時才創建),Broadcaster被創建為一個單例。使用了延遲初始化(.Net4中新增功能),來推遲其創建時間直到需要它為止。這是為了確保在計時器開始之前就有集線器的實例被成功創建完畢。
調用客戶端的updateShape功能被移出集線器的updateModel方法,所有消息傳入后它將不再立即被調用。相反,需要發送至客戶端的消息會以每秒25次的頻率進行發送。Broadcaster類中的_broadcastLoop計時器來承擔發送頻率的管理功能。
最終,集線器並不直接調用客戶端方法,Broadcaster類需要使用GlobalHost來獲得一個引用當前正在運行的操作集線器(_hubContext)。
2.按F5啟動應用程序,復制窗口並拖動,將不會同上一節中的效果有太大差別。但在后台,我們已經對發送到客戶端的消息進行了節流限制。
為客戶端加入流暢的動畫效果
這個應用程序已經很完善了,但我們還需要做進一步的改進。客戶端的形狀移動是由接收到服務器消息而進行的,我們將使用jQuery UI庫的animate功能來優化形狀的移動效果,而不是直接使用服務器提供的新位置來改變形狀的當前位置。
1.使用下面的代碼更新HTML頁面:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>SignalR MoveShape Demo</title> 5 <style> 6 #shape { 7 width: 100px; 8 height: 100px; 9 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。
作者:帕特里克·弗萊徹 -帕特里克·弗萊徹是ASP.NET開發團隊的程序員,作家,目前正在SignalR項目工作。