• SignalR学习笔记(二)高并发应用


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

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

    项目的需求如下

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

    创建项目

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

    引入SignalR库及jQuery UI库

    打开Package Manage Console面板

    运行一下2个命令

    Install-package Microsoft.AspNet.SignalR

    Install-package jQuery.UI.Combined

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

    添加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个用户同时访问的情况

     

    效率问题

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

    Console.log($shape.offset())

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

     

    也就是说移动一个微小的距离,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 });
    
    };
  • 相关阅读:
    【并查集】亲戚
    【图论】Car的旅行线路 NOIP 2001
    【贪心】排座椅
    【DP】花店橱窗布置
    【NOIP】NOIP考纲总结+NOIP考前经验谈
    【NOIP】考前须知
    NOIP 2016 PJ T4 魔法阵
    NOIP 2016 PJ T3 海港
    【高精度】麦森数 NOIP 2003
    【带权并查集】食物链 NOIP 2001
  • 原文地址:https://www.cnblogs.com/lwqlun/p/9095088.html
Copyright © 2020-2023  润新知