SignalR学习笔记(二)高并发应用
虽然SignalR借助Websocket提供了很强大的实时通讯能力,但是在有些实时通讯非常频繁的场景之下,如果使用不当,还是会导致服务器,甚至客户端浏览器崩溃。
以下是一个实时拖拽方块项目的优化过程
项目的需求如下
- 在网页中显示一个红色的可拖拽方块
- 一个用户拖拽该方块,该方块在其他用户客户端浏览器中的位置也会相应改变

创建项目
使用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学习笔记(二)高并发应用的更多相关文章
- spring cloud(学习笔记)高可用注册中心(Eureka)的实现(二)
绪论 前几天我用一种方式实现了spring cloud的高可用,达到两个注册中心,详情见spring cloud(学习笔记)高可用注册中心(Eureka)的实现(一),今天我意外发现,注册中心可以无限 ...
- JDBC学习笔记二
JDBC学习笔记二 4.execute()方法执行SQL语句 execute几乎可以执行任何SQL语句,当execute执行过SQL语句之后会返回一个布尔类型的值,代表是否返回了ResultSet对象 ...
- [Firefly引擎][学习笔记二][已完结]卡牌游戏开发模型的设计
源地址:http://bbs.9miao.com/thread-44603-1-1.html 在此补充一下Socket的验证机制:socket登陆验证.会采用session会话超时的机制做心跳接口验证 ...
- muduo学习笔记(二)Reactor关键结构
目录 muduo学习笔记(二)Reactor关键结构 Reactor简述 什么是Reactor Reactor模型的优缺点 poll简述 poll使用样例 muduo Reactor关键结构 Chan ...
- tensorflow学习笔记二:入门基础 好教程 可用
http://www.cnblogs.com/denny402/p/5852083.html tensorflow学习笔记二:入门基础 TensorFlow用张量这种数据结构来表示所有的数据.用一 ...
- Java IO学习笔记二:DirectByteBuffer与HeapByteBuffer
作者:Grey 原文地址:Java IO学习笔记二:DirectByteBuffer与HeapByteBuffer ByteBuffer.allocate()与ByteBuffer.allocateD ...
- 纯JS实现KeyboardNav(学习笔记)二
纯JS实现KeyboardNav(学习笔记)二 这篇博客只是自己的学习笔记,供日后复习所用,没有经过精心排版,也没有按逻辑编写 这篇主要是添加css,优化js编写逻辑和代码排版 GitHub项目源码 ...
- WPF的Binding学习笔记(二)
原文: http://www.cnblogs.com/pasoraku/archive/2012/10/25/2738428.htmlWPF的Binding学习笔记(二) 上次学了点点Binding的 ...
- AJax 学习笔记二(onreadystatechange的作用)
AJax 学习笔记二(onreadystatechange的作用) 当发送一个请求后,客户端无法确定什么时候会完成这个请求,所以需要用事件机制来捕获请求的状态XMLHttpRequest对象提供了on ...
随机推荐
- 与下位机或设备的通信解析优化的一点功能:T4+动态编译
去年接触的一个项目中,需要通过TCP与设备进行对接的,传的是Modbus协议的数据,然后后台需要可以动态配置协议解析的方式,即寄存器的解析方式,,配置信息有:Key,数据Index,源数据类型 ...
- AI零基础入门之人工智能开启新时代—下篇
人工智能概述 人工智能的定义 · 人工智能是通过机器来模拟人类认识能力的一种科技能力 · 人工智能最核心的能力就是根据给定的输入做出判断或预测 · 思考:通过什么途径才能让机器具备这样的能力? · 举 ...
- tensorflow 使用 1 常量,变量
import tensorflow as tf #创建一个常量 op 一行二列 m1 = tf.constant([[3, 3]]) #创建一个常量 op 二行一列 m2 = tf.constant( ...
- Luogu 3384 【模板】树链剖分
题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式 ...
- C语言可变参数
前段时候在实现利用redis进行的一个数据库比对的功能,稍微去分析了一下redis里面的源代码,然后发现其中的发送命令接口声明如下: void *redisCommand(redisConnect * ...
- Win 10 Revit 2019 安装过程,亲自踩的一遍坑,有你想要的细节
首先就是安装吖,不管是管理员权限还是普通权限,都是以下这个问题,跟权限没关系 failed to load .....revitcontentpackui.dll (126) 尝试了网上能查到的各种方 ...
- ORM框架之SQLAchemy
SQLAchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,即:将对象转换成SQL,然后使用数据API执行SQL并获取执行结果. 1.安装 ...
- git 本地同步分支数,删除远程已经删除掉的多余分支
git remote show orgin (展示当前本地分支和远程上的分支差异,多余分支后会被标注 use 'git remote prune' to remove.) git remote pr ...
- 微信小程序学习笔记(三)
一般setData方法多用于点击后改变页面信息或者刷新后与后台交互获取最新的信息 注意: 直接修改 this.data 而不调用 this.setData 是无法改变页面的状态的,还会造成数据不一致 ...
- Springboot关于脚本脚本启动的项目:
#!/bin/bash if [ -f ~/.bash_profile ];then . ~/.bash_profilefi JAVA_HOME=/usr/local/usr_software/jd ...