本教程演示如何创建使用 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 Hub 类” (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.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>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
        </style>
    </head>
    <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 代码创建名为 的shape红色div。 它使用 jQuery 库启用形状的拖动行为, drag 并使用 事件将形状的位置发送到服务器。

    你可以运行应用以使其正常工作。 在浏览器窗口周围拖动形状时,形状也会在其他浏览器中移动。

  • 在工具栏中,打开 “脚本调试 ”,然后选择“播放”按钮以在调试模式下运行应用程序。

    此时会打开一个浏览器窗口,右上角有红色形状。

  • 复制页面的 URL。

  • 打开另一个浏览器,并将 URL 粘贴到地址栏中。

  • 在其中一个浏览器窗口中拖动形状。 另一个浏览器窗口中的形状紧随其后。

    虽然应用程序使用此方法运行,但它不是建议的编程模型。 发送的消息数没有上限。 因此,客户端和服务器因消息而不知所措,性能会降低。 此外,应用还会在客户端上显示不连续的动画。 之所以出现这种混蛋动画,是因为形状会按每个方法即时移动。 最好是形状平稳地移动到每个新位置。 接下来,你将了解如何解决这些问题。

    添加客户端循环

    在每个鼠标移动事件上发送形状的位置会产生不必要的网络流量。 应用需要限制来自客户端的消息。

    使用 javascript setInterval 函数设置一个循环,该循环以固定速率将新位置信息发送到服务器。 此循环是“游戏循环”的基本表示形式。它是一个反复调用的函数,用于驱动游戏的所有功能。

  • Default.html 文件中的客户端代码替换为以下代码:

    <!DOCTYPE html>
    <title>SignalR MoveShape Demo</title>
    <style>
        #shape {
            width: 100px;
            height: 100px;
            background-color: #FF0000;
    </style>
    </head>
    <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
                    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>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
        </style>
    </head>
    <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 创建的 ShootR 游戏

    有关 SignalR 的详细信息,请参阅以下资源:

  • SignalR 项目

  • SignalR GitHub 和示例

  • SignalR Wiki

    在本教程中,你将了解:

  • 已创建基本应用程序
  • 应用启动时映射到中心
  • 添加了客户端
  • 添加了客户端循环
  • 添加了服务器循环
  • 添加了平滑动画
  • 请继续学习下一篇文章,了解如何创建使用 ASP.NET SignalR 2 提供服务器广播功能的 Web 应用程序。

    SignalR 2 和服务器广播

  •