SignalR学习笔记(二)高并发应用 创建项目 引入SignalR库及jQuery UI库 添加Owin启动类,启用SignalR 添加Position类 添加MoveShapeHub 添加前台页面 当前效果 效率问题 如何改善效率 位置更新不连续

SignalR学习笔记(二)高并发应用
创建项目
引入SignalR库及jQuery UI库
添加Owin启动类,启用SignalR
添加Position类
添加MoveShapeHub
添加前台页面
当前效果
效率问题
如何改善效率
位置更新不连续

虽然SignalR借助Websocket提供了很强大的实时通讯能力,但是在有些实时通讯非常频繁的场景之下,如果使用不当,还是会导致服务器,甚至客户端浏览器崩溃。

以下是一个实时拖拽方块项目的优化过程

项目的需求如下

  1. 在网页中显示一个红色的可拖拽方块
  2. 一个用户拖拽该方块,该方块在其他用户客户端浏览器中的位置也会相应改变

SignalR学习笔记(二)高并发应用
创建项目
引入SignalR库及jQuery UI库
添加Owin启动类,启用SignalR
添加Position类
添加MoveShapeHub
添加前台页面
当前效果
效率问题
如何改善效率
位置更新不连续

创建项目

使用VS创建一个空的Web项目

SignalR学习笔记(二)高并发应用
创建项目
引入SignalR库及jQuery UI库
添加Owin启动类,启用SignalR
添加Position类
添加MoveShapeHub
添加前台页面
当前效果
效率问题
如何改善效率
位置更新不连续

SignalR学习笔记(二)高并发应用
创建项目
引入SignalR库及jQuery UI库
添加Owin启动类,启用SignalR
添加Position类
添加MoveShapeHub
添加前台页面
当前效果
效率问题
如何改善效率
位置更新不连续

引入SignalR库及jQuery UI库

打开Package Manage Console面板

SignalR学习笔记(二)高并发应用
创建项目
引入SignalR库及jQuery UI库
添加Owin启动类,启用SignalR
添加Position类
添加MoveShapeHub
添加前台页面
当前效果
效率问题
如何改善效率
位置更新不连续

运行一下2个命令

Install-package Microsoft.AspNet.SignalR

Install-package jQuery.UI.Combined

安装完成之后,解决方案结构如下

SignalR学习笔记(二)高并发应用
创建项目
引入SignalR库及jQuery UI库
添加Owin启动类,启用SignalR
添加Position类
添加MoveShapeHub
添加前台页面
当前效果
效率问题
如何改善效率
位置更新不连续

添加Owin启动类,启用SignalR

和学习笔记(一)中的步骤一样,添加一个Owin Startup Class, 命名为Startup.cs,  并在Configuration启用SignalR

using Microsoft.Owin;

using Owin;

 

[assembly: OwinStartup(typeof(MoveShape.Startup))]

 

namespace MoveShape

{

    public class Startup

    {

        public void Configuration(IAppBuilder app)

        {

            app.MapSignalR();

        }

    }

}


 

添加Position类

这里我们需要一个新建一个类来传递方法的位置信息

using Newtonsoft.Json; 

namespace MoveShape

{

    public class Position

    {

        [JsonProperty("left")]

        public double Left { get; set; }

 

        [JsonProperty("top")]

        public double Top { get; set; }

 

        [JsonProperty("lastUpdatedBy")]

        public string LastUpdatedBy { get; set; }

    }

}


添加MoveShapeHub

我们需要创建一个Hub来传递当前方块的位置信息

using Microsoft.AspNet.SignalR;

 

namespace MoveShape

{

    public class MoveShapeHub : Hub

    {

        public void MovePosition(Position model)

        {

            model.LastUpdatedBy = Context.ConnectionId;

            Clients.AllExcept(Context.ConnectionId).updatePosition(model);

        }

    }

}


 

当前用户在移动方块,除了当前用户之外的其他用户都需要更新方块位置,所以这里使用了

Clients.AllExcept方法,将当前用户从排除列表里面去除掉。

SignalR学习笔记(一)中有说道,当用户客户端与Hub连接成功之后,Hub会分配一个全局唯一的ConnectionId给当前用户客户端,所以Context中的ConnectionId即表示当前用户。

Clients对象提供的所有筛选客户端方法如下

  • Client.All – 向所有和Hub连接成功的客户端发送消息
  • Client.AllExcept – 向除了指定客户端外的用户客户端发送消息
  • Client.Client – 向指定的一个客户端发送消息
  • Client.Clients – 向指定的多个客户端发送消息

添加前台页面

前台页面是用jQuery UI的Draggable功能实现拖拽,在Drag事件里可以获取到当前方块的位置,所以在这里我们可以将位置发送到MoveShapeHub中

<!DOCTYPE html>

<html>

<head>

    <title>SignalR MoveShape Demo</title>

    <style>

        #shape {

             100px;

            height: 100px;

            background-color: #FF0000;

        }

    </style>

</head>

<body>

    <script src="Scripts/jquery-1.12.4.min.js"></script>

    <script src="Scripts/jquery-ui-1.12.1.min.js"></script>

    <script src="Scripts/jquery.signalR-2.2.0.js"></script>

    <script src="/signalr/hubs"></script>

    <script>

        $(function () {

 

            //创建Hub代理

            var moveShapeHub = $.connection.moveShapeHub,

            $shape = $("#shape"),

            shapeModel = {

                left: 0,

                top: 0

            };

 

            //客户端接受到位置变动消息,执行的方法

            moveShapeHub.client.updatePosition = function (model) {

                shapeModel = model;

                $shape.css({ left: model.left, top: model.top });

            };

 

            $.connection.hub.start().done(function () {

                $shape.draggable({

                    drag: function () {

                        shapeModel = $shape.offset();

 

                        //当发生拖拽的之后,把方块当前位置发送到Hub

                        moveShapeHub.server.movePosition(shapeModel);

                    }

                });

            });

        });

    </script>

 

    <div id="shape" />

</body>

</html>


 

当前效果

分别在2个浏览器中启动MoveShape.html, 模拟2个用户同时访问的情况

 SignalR学习笔记(二)高并发应用
创建项目
引入SignalR库及jQuery UI库
添加Owin启动类,启用SignalR
添加Position类
添加MoveShapeHub
添加前台页面
当前效果
效率问题
如何改善效率
位置更新不连续

效率问题

下面我们在Drag事件里面添加日志代码

Console.log($shape.offset())

 然后刷新页面,打开Chrome的开发者工具的console面板,然后移动方块,你会发现每做一次微小的移动,代码都会执行一次。

 SignalR学习笔记(二)高并发应用
创建项目
引入SignalR库及jQuery UI库
添加Owin启动类,启用SignalR
添加Position类
添加MoveShapeHub
添加前台页面
当前效果
效率问题
如何改善效率
位置更新不连续

也就是说移动一个微小的距离,SignalR的Hub中的MovePosition方法都会执行一边,所有观看这个页面的用户都会执行一次UpdatePosition方法来同步位置,在用户比较少的情况下可能问题还不大,但是一旦用户数量增多,这个就是一个极大的性能黑洞。

如何改善效率

对于如何改善效率,我们可以分别从客户端和服务器端入手

客户端

在客户端,我们可以添加一个定时器,每隔一个时间间隔,向服务器更新一次方块的位置,这样更新位置的请求数量就大幅减少了

<!DOCTYPE html>

<html>

<head>

    <title>SignalR MoveShape Demo</title>

    <style>

        #shape {

             100px;

            height: 100px;

            background-color: #FF0000;

        }

    </style>

</head>

<body>

    <script src="Scripts/jquery-1.12.4.min.js"></script>

    <script src="Scripts/jquery-ui-1.12.1.min.js"></script>

    <script src="Scripts/jquery.signalR-2.2.2.js"></script>

    <script src="/signalr/hubs"></script>

    <script>

        $(function () {

 

            //创建Hub代理

            var moveShapeHub = $.connection.moveShapeHub,

            $shape = $("#shape"),

 

            //每200毫秒,向服务器同步一次位置

            interval = 200,

 

            //方块是否在移动

            moved = false,

            shapeModel = {

                left: 0,

                top: 0

            };

 

            //客户端接受到位置变动消息,执行的方法

            moveShapeHub.client.updatePosition = 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;

                    }

                });

 

                //添加定时器, 每个200毫秒, 向服务器同步一次位置

                setInterval(updateServerModel, interval);

            });

 

            function updateServerModel() {

                if (moved) {

                    console.log($shape.offset());

 

                    moveShapeHub.server.movePosition(shapeModel);

 

                    //同步完毕之后, 设置moved标志为false

                    moved = false;

                }

            }

        });

    </script>

 

    <div id="shape" />

</body>

</html>


 

服务器端

服务器端,可以采取和客户端差不多的思路,加入一个定时器,减少同步方块位置的次数。

using Microsoft.AspNet.SignalR;

using System;

using System.Threading;

 

namespace MoveShape

{

    public class Broadcaster

    {

        private readonly static Lazy<Broadcaster> _instance =

            new Lazy<Broadcaster>(() => new Broadcaster());

       

        //每隔40毫秒,执行一次同步操作

        private readonly TimeSpan BroadcastInterval =

            TimeSpan.FromMilliseconds(40);

        private readonly IHubContext _hubContext;

        private Timer _broadcastLoop;

        private Position _model;

        private bool _modelUpdated;

        public Broadcaster()

        {

            _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();

            _model = new Position();

            _modelUpdated = false;

 

            //添加定时器,每隔一个时间间隔,执行一次同步位置方法

            _broadcastLoop = new Timer(

                BroadcastShape,

                null,

                BroadcastInterval,

                BroadcastInterval);

        }

        public void BroadcastShape(object state)

        {

            if (_modelUpdated)

            {

                _hubContext.Clients.AllExcept(_model.LastUpdatedBy).updatePosition(_model);

                _modelUpdated = false;

            }

        }

        public void UpdatePosition(Position position)

        {

            _model = position;

            _modelUpdated = true;

        }

        public static Broadcaster Instance

        {

            get

            {

                return _instance.Value;

            }

        }

    }

 

    public class MoveShapeHub : Hub

    {

        private Broadcaster _broadcaster;

        public MoveShapeHub()

            : this(Broadcaster.Instance)

        {

        }

        public MoveShapeHub(Broadcaster broadcaster)

        {

            _broadcaster = broadcaster;

        }

        public void UpdateModel(Position position)

        {

            position.LastUpdatedBy = Context.ConnectionId;

            // Update the shape model within our broadcaster

            _broadcaster.UpdatePosition(position);

        }

    }

}


 

位置更新不连续

由于加入定时器,导致方块位置更新不连续,界面上看起来方块的移动是断断续续的。

这里的解决方案是,在客户端可以使用jQuery的animate方法,填补方块移动不连续的部分

moveShapeHub.client.updatePosition = function (model) {

    shapeModel = model;

    $shape.animate(shapeModel, { duration: 200, queue: false });

};