虽然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 {

            width: 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 {

            width: 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 });

};

SignalR学习笔记(二)高并发应用的更多相关文章

  1. spring cloud(学习笔记)高可用注册中心(Eureka)的实现(二)

    绪论 前几天我用一种方式实现了spring cloud的高可用,达到两个注册中心,详情见spring cloud(学习笔记)高可用注册中心(Eureka)的实现(一),今天我意外发现,注册中心可以无限 ...

  2. JDBC学习笔记二

    JDBC学习笔记二 4.execute()方法执行SQL语句 execute几乎可以执行任何SQL语句,当execute执行过SQL语句之后会返回一个布尔类型的值,代表是否返回了ResultSet对象 ...

  3. [Firefly引擎][学习笔记二][已完结]卡牌游戏开发模型的设计

    源地址:http://bbs.9miao.com/thread-44603-1-1.html 在此补充一下Socket的验证机制:socket登陆验证.会采用session会话超时的机制做心跳接口验证 ...

  4. muduo学习笔记(二)Reactor关键结构

    目录 muduo学习笔记(二)Reactor关键结构 Reactor简述 什么是Reactor Reactor模型的优缺点 poll简述 poll使用样例 muduo Reactor关键结构 Chan ...

  5. tensorflow学习笔记二:入门基础 好教程 可用

    http://www.cnblogs.com/denny402/p/5852083.html tensorflow学习笔记二:入门基础   TensorFlow用张量这种数据结构来表示所有的数据.用一 ...

  6. Java IO学习笔记二:DirectByteBuffer与HeapByteBuffer

    作者:Grey 原文地址:Java IO学习笔记二:DirectByteBuffer与HeapByteBuffer ByteBuffer.allocate()与ByteBuffer.allocateD ...

  7. 纯JS实现KeyboardNav(学习笔记)二

    纯JS实现KeyboardNav(学习笔记)二 这篇博客只是自己的学习笔记,供日后复习所用,没有经过精心排版,也没有按逻辑编写 这篇主要是添加css,优化js编写逻辑和代码排版 GitHub项目源码 ...

  8. WPF的Binding学习笔记(二)

    原文: http://www.cnblogs.com/pasoraku/archive/2012/10/25/2738428.htmlWPF的Binding学习笔记(二) 上次学了点点Binding的 ...

  9. AJax 学习笔记二(onreadystatechange的作用)

    AJax 学习笔记二(onreadystatechange的作用) 当发送一个请求后,客户端无法确定什么时候会完成这个请求,所以需要用事件机制来捕获请求的状态XMLHttpRequest对象提供了on ...

随机推荐

  1. IntelliJ IDE 基础经验备案

    1.配置本地的JAVA环境 2.配置本地安装的Maven环境 详情 1.配置本地的JAVA环境 准备: 本地已经安装java环境,目录:C:\Program Files\Java\jdk1.8.0_1 ...

  2. 在Eclipse中使用git把项目导入到git中--转载

    [转载出处注明:http://www.zhangxiaofu.cn/java/commonTools/2015/0607/764.html] 一.原有项目:  项目名为TestGit 二.在osc@g ...

  3. hdu4811-Ball(2013ACM/ICPC亚洲区南京站现场赛)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4811 题目描述: Problem Description Jenny likes balls. He ...

  4. BlockChain:Py实现区块链简单场景应用:程序猿记录在区块里的收入记录图——Jason niu

    # -*- coding: utf-8 -*- ''' Created on 2018年3月11日 @author: Jason niu ''' import hashlib #该模块实现了诸多安全哈 ...

  5. onenote使用教程

    1. OneNote的基础功能 随处可以记录笔记 收集页面 微信关注公众号 [微软云笔记] IE和Chrome等浏览器扩展-OneNote Clipper 通过绘图实现手绘效果 插入音频与视频 实现多 ...

  6. 在WINDOWS中安装使用SIGPACK(MinGW64+Sublime Text3 &Visual Studio)

    本文介绍在Windows下安装使用SigPack库,涉及Armadillo.OpenBLAS安装使用说明,最终实现对SigPack示例CPP基于MinGW64在Sublime Text3下的编译运行, ...

  7. spring-boot-starter-actouator2.1.4与c3p0版本0.9.1.2冲突

    报错前的pom文件: <?xml version="1.0" encoding="UTF-8"?><project xmlns="h ...

  8. ASP.NET Aries 开发框架(已支持.NET Core)

    背景: 当年,在卖弄与开源QBlog时,也曾想过把QBlog的开发理念整理整理,独立一个框架来开源. 不过,人越长大就越憔悴,激情终敌不过疲惫的惰性,最终无痕而终,连3.0的版本也没开源出来. 关于框 ...

  9. 【C语言编程练习】7.2动态数列排列

    1. 题目要求 编写这样一个C程序,实现这样的功能:从键盘输入任意个整数,以0作为结束标志,对于这个整数序列从小到大排序,并输出排序后结果 2. 题目分析 不得不说,我确实对这类问题毫无思路呢!应该也 ...

  10. APM飞控学习之路的资料

    飞控学习之路的资料 https://blog.csdn.net/u010682510 博客资料 https://blog.csdn.net/qq_26573899/article/category/7 ...