教程:使用 SignalR 2 创建高频率实时应用
本教程介绍如何创建使用 ASP.NET SignalR 2 提供高频率消息传送功能的 Web 应用程序。 在这种情况下,“高频率消息传送”表示服务器以固定速率发送更新。 每秒最多发送 10 条消息。
你创建的应用程序显示用户可以拖动的形状。 服务器使用计时更新更新更新所有连接的浏览器中形状的位置,以匹配拖动的形状的位置。
本教程中介绍的概念具有实时游戏和其他模拟应用程序中的应用程序。
在本教程中,你将了解:
- 设置项目
- 创建基本应用程序
- 应用启动时映射到中心
- 添加客户端
- 运行应用
- 添加客户端循环
- 添加服务器循环
- 添加平滑动画
警告
本文档不适用于最新版本的 SignalR。 查看 ASP.NET Core SignalR。
先决条件
- 带有 ASP.NET 和 Web 开发工作负荷的 Visual Studio 2017。
设置项目
在本部分中,将在 Visual Studio 2017 中创建项目。
本部分介绍如何使用 Visual Studio 2017 创建空 ASP.NET Web 应用程序并添加 SignalR 和 jQuery.UI 库。
在 Visual Studio 中,创建 ASP.NET Web 应用程序。
在 “新建 ASP.NET Web 应用程序 - MoveShapeDemo ”窗口中,保留 “空 ”,然后选择“ 确定”。
在“解决方案资源管理器”中右键单击该项目,然后选择“添加”>和“新建项”。
在“添加新项 - MoveShapeDemo”中,选择“已安装>的 Visual C#>Web>SignalR”,然后选择“SignalR 中心类”(v2)。
将类 命名为 MoveShapeHub 并将其添加到项目中。
此步骤将创建 MoveShapeHub.cs 类文件。 同时,它会向项目添加一组支持 SignalR 的脚本文件和程序集引用。
选择“工具”>“NuGet 包管理器”>“包管理器控制台”。
在程序包管理器控制台中,运行以下命令:
Install-Package jQuery.UI.Combined
该命令安装 jQuery UI 库。 使用它对形状进行动画处理。
在解决方案资源管理器中,展开“脚本”节点。
jQuery、jQueryUI 和 SignalR 的脚本库在项目中可见。
创建基本应用程序
在本部分中,将创建浏览器应用程序。 应用在每个鼠标移动事件期间将形状的位置发送到服务器。 服务器实时将此信息广播到所有其他连接的客户端。 在后面的部分中,你将了解有关此应用程序的详细信息。
打开 MoveShapeHub.cs 文件。
将MoveShapeHub.cs文件中的代码替换为以下代码:
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 中心的实现。 与 SignalR 入门教程一样,中心具有客户端直接调用的方法。 在这种情况下,客户端会将形状的新 X 和 Y 坐标的对象发送到服务器。 这些坐标将广播到所有其他连接的客户端。 SignalR 使用 JSON 自动序列化此对象。
应用将 ShapeModel
对象发送到客户端。 它具有成员来存储形状的位置。 服务器上的对象版本还有一个成员,用于跟踪要存储的客户端数据。 此对象可防止服务器将客户端的数据发送回自身。 此成员使用该 JsonIgnore
属性来防止应用程序序列化数据并将其发送回客户端。
应用启动时映射到中心
接下来,在应用程序启动时设置到中心的映射。 在 SignalR 2 中,添加 OWIN 启动类将创建映射。
在“解决方案资源管理器”中右键单击该项目,然后选择“添加”>和“新建项”。
在“添加新项 - MoveShapeDemo”中选择“已安装>的 Visual C#>Web”,然后选择“OWIN 启动类”。
将类 命名为 Startup ,然后选择“ 确定”。
将Startup.cs文件中的默认代码替换为以下代码:
using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(MoveShapeDemo.Startup))] namespace MoveShapeDemo { public class Startup { public void Configuration(IAppBuilder app) { // Any connection or hub wire up and configuration should go here app.MapSignalR(); } } }
当应用执行Configuration
该方法时,OWIN 启动类将调用MapSignalR
。 该应用使用 OwinStartup
程序集属性将类添加到 OWIN 的启动进程。
添加客户端
添加客户端的 HTML 页。
在解决方案资源管理器中,右键单击项目并选择“添加>HTML 页面”。
将页面 命名为“默认值 ”,然后选择“ 确定”。
在解决方案资源管理器中,右键单击Default.html并选择“设为起始页”。
将Default.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.10.2.min.js"></script> <script src="Scripts/jquery-ui-1.10.4.min.js"></script> <script src="Scripts/jquery.signalR-2.1.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>
在解决方案资源管理器中,展开“脚本”。
jQuery 和 SignalR 的脚本库在项目中可见。
重要
包管理器安装 SignalR 脚本的更高版本。
更新代码块中的脚本引用,以对应于项目中脚本文件的版本。
此 HTML 和 JavaScript 代码创建一个名为红色div
的 。shape
它使用 jQuery 库启用形状的拖动行为,并使用 drag
事件将形状的位置发送到服务器。
运行应用
可以运行应用以使其正常工作。 在浏览器窗口周围拖动形状时,该形状也会在其他浏览器中移动。
在工具栏中,打开 脚本调试 ,然后选择播放按钮以在调试模式下运行应用程序。
此时会打开浏览器窗口,右上角带有红色形状。
复制页面的 URL。
打开另一个浏览器并将 URL 粘贴到地址栏中。
在浏览器窗口之一中拖动形状。 其他浏览器窗口中的形状如下所示。
虽然应用程序使用此方法,但它不是推荐的编程模型。 发送的消息数没有上限。 因此,客户端和服务器因消息和性能下降而不知所措。 此外,应用在客户端上显示不相交的动画。 发生这种混蛋动画的原因是形状会立即由每个方法移动。 如果形状顺利地移动到每个新位置,则最好。 接下来,你将了解如何解决这些问题。
添加客户端循环
在每个鼠标移动事件上发送形状的位置会创建不必要的网络流量。 应用需要限制来自客户端的消息。
使用 javascript setInterval
函数设置一个循环,以固定速率将新位置信息发送到服务器。 此循环是“游戏循环”的基本表示形式。它是一个反复调用的函数,可驱动游戏的所有功能。
将Default.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.10.2.min.js"></script> <script src="Scripts/jquery-ui-1.10.4.min.js"></script> <script src="Scripts/jquery.signalR-2.1.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; $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
,该函数就会将位置数据发送到服务器。选择“播放”按钮以启动应用程序
复制页面的 URL。
打开另一个浏览器并将 URL 粘贴到地址栏中。
在浏览器窗口之一中拖动形状。 其他浏览器窗口中的形状如下所示。
由于应用会限制发送到服务器的消息数,因此动画一开始不会像平整一样显示。
添加服务器循环
在当前应用程序中,从服务器发送到客户端的消息在收到时会一样频繁地发出。 此网络流量在客户端上出现类似问题。
应用可以比所需的消息更频繁地发送消息。 因此,连接可能会被洪水淹没。 本部分介绍如何更新服务器以添加限制传出消息速率的计时器。
用以下代码替换
MoveShapeHub.cs
的内容: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; } } }
选择“播放”按钮以启动应用程序。
复制页面的 URL。
打开另一个浏览器并将 URL 粘贴到地址栏中。
在浏览器窗口之一中拖动形状。
此代码扩展客户端以添加 Broadcaster
类。 新类使用 Timer
.NET Framework 中的类限制传出消息。
很高兴知道中心本身是暂时性的。 每次需要时都会创建它。 因此,应用将创建 Broadcaster
为单一实例。 它使用延迟初始化来延迟 Broadcaster
创建,直到需要它。 这可以保证应用在启动计时器之前完全创建第一个中心实例。
然后,对客户端 UpdateShape
函数的 UpdateModel
调用已移出中心的方法。 每当应用收到传入消息时,它将不再立即调用。 相反,应用以每秒 25 次调用的速度将消息发送到客户端。 进程由 _broadcastLoop
计时器从类内部 Broadcaster
进行管理。
最后,类需要获取对当前操作_hubContext
中心的引用,Broadcaster
而不是直接从中心调用客户端方法。 它获取具有 .. 的 GlobalHost
引用。
添加平滑动画
应用程序几乎已完成,但我们可以再改进一次。 应用在客户端上移动形状以响应服务器消息。 使用 JQuery UI 库的 animate
函数,而不是将形状的位置设置为服务器给出的新位置。 它可以在其当前位置和新位置之间平滑移动形状。
更新Default.html文件中的客户端
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.10.2.min.js"></script> <script src="Scripts/jquery-ui-1.10.4.min.js"></script> <script src="Scripts/jquery.signalR-2.1.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>
选择“播放”按钮以启动应用程序。
复制页面的 URL。
打开另一个浏览器并将 URL 粘贴到地址栏中。
在浏览器窗口之一中拖动形状。
另一个窗口中形状的移动似乎不那么混蛋。 应用在一段时间内内插其移动,而不是为每个传入消息设置一次。
此代码将形状从旧位置移动到新位置。 服务器在动画间隔过程中提供形状的位置。 在这种情况下,这是 100 毫秒。 应用清除在新动画开始之前在形状上运行的任何以前的动画。
获取代码
其他资源
有关 SignalR 的详细信息,请参阅以下资源:
后续步骤
在本教程中,你将了解:
- 设置项目
- 创建了基础应用程序
- 应用启动时映射到中心
- 添加了客户端
- 运行应用
- 添加了客户端循环
- 添加了服务器循环
- 添加了平滑动画
请继续学习下一篇文章,了解如何创建使用 ASP.NET SignalR 2 提供服务器广播功能的 Web 应用程序。