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

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using Microsoft.Owin;
 
using Owin;
 
  
 
[assembly: OwinStartup(typeof(MoveShape.Startup))]
 
  
 
namespace MoveShape
 
{
 
    public class Startup
 
    {
 
        public void Configuration(IAppBuilder app)
 
        {
 
            app.MapSignalR();
 
        }
 
    }
 
}


 

添加Position类

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

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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来传递当前方块的位置信息

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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中

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<!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方法来同步位置,在用户比较少的情况下可能问题还不大,但是一旦用户数量增多,这个就是一个极大的性能黑洞。

 

如何改善效率

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

 

客户端#

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

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<!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>


 

 

服务器端#

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

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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方法,填补方块移动不连续的部分

1
2
3
4
5
6
7
moveShapeHub.client.updatePosition = function (model) {
 
    shapeModel = model;
 
    $shape.animate(shapeModel, { duration: 200, queue: false });
 
};
posted @   LamondLu  阅读(750)  评论(1编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示
主题色彩