假设一种情景:TCP服务器有1万个客户端连接,如果客户端5秒钟不发数据,则要断开。服务端如何检测客户端是否超时?这看起来是一个非常简单的问题,其实不然!
最简单的处理方法是:启动一个线程,每隔一段时间,检查每个连接是否超时。每次处理需要1万次检查。计算量太大!检查的时间间隔不能太小,否则大大增加计算量;如果间隔时间太大,超时误差会增大。
本文提出一种新颖的处理方法,就是针对这个看似简单而不易解决的问题!(以下用socket表示一个客户端连接)
1 内存布局图
假设socket3有新的数据到达,需要更新socket3所在的时间轴,处理逻辑如下:
2 处理过程分析:
基本的处理思路就是增加时间轴概念。将socket按最后更新时间排序。因为时间是连续的,不可能将时间分割太细。首先将时间离散,比如属于同一秒内的更新,被认为是属于同一个时间点。离散的时间间隔称为时间刻度,该刻度值可以根据具体情况调整。刻度值越小,超时计算越精确;但是计算量增大。如果时间刻度为10毫秒,则一秒的时间长度被划分为100份。所以需要对更新时间做规整,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23DateTime CreateNow() { DateTime now = DateTime.Now; int m = 0; if(now.Millisecond != 0) { if(_minimumScaleOfMillisecond == 1000) { now = now.AddSeconds(1); //尾数加1,确保超时值大于 给定的值 } else { //如果now.Millisecond为16毫秒,精确度为10毫秒。则转换后为20毫秒 m = now.Millisecond - now.Millisecond % _minimumScaleOfMillisecond + _minimumScaleOfMillisecond; if(m>=1000) { m -= 1000; now = now.AddSeconds(1); } } } return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second,m); }
属于同一个时间刻度的socket,被放入在一个哈希表中(见图中Group)。存放socket的类如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class SameTimeKeyGroup<T> { DateTime _timeStamp; public DateTime TimeStamp => _timeStamp; public SameTimeKeyGroup(DateTime time) { _timeStamp = time; } public HashSet<T> KeyGroup { get; set; } = new HashSet<T>(); public bool ContainKey(T key) { return KeyGroup.Contains(key); } internal void AddKey(T key) { KeyGroup.Add(key); } internal bool RemoveKey(T key) { return KeyGroup.Remove(key); } }
定义一个List表示时间轴:
1List<SameTimeKeyGroup<T>> _listTimeScale = new List<SameTimeKeyGroup<T>>();
在_listTimeScale 前端的时间较旧,所以链表前端就是有可能超时的socket。
当有socket需要更新时,需要快速知道socket所在的group。这样才能将socket从旧的group移走,再添加到新的group中。需要新增一个链表:
1Dictionary<T, SameTimeKeyGroup<T>> _socketToSameTimeKeyGroup = new Dictionary<T, SameTimeKeyGroup<T>>();
2.1 当socket有新的数据到达时,处理步骤:
- 查找socket的上一个群组。如果该群组对应的时刻和当前时刻相同(时间都已经离散,才有可能相同),无需更新时间轴。
- 从旧的群组删除,增加到新的群组。
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
35public void UpdateTime(T key) { DateTime now = CreateNow(); //是否已存在,从上一个时间群组删除 if (_socketToSameTimeKeyGroup.ContainsKey(key)) { SameTimeKeyGroup<T> group = _socketToSameTimeKeyGroup[key]; if (group.ContainKey(key)) { if (group.TimeStamp == now) //同一时间更新,无需移动 { return; } else { group.RemoveKey(key); _socketToSameTimeKeyGroup.Remove(key); } } } //从超时组 删除 _timeoutSocketGroup.Remove(key); //加入到新组 SameTimeKeyGroup<T> groupFromScaleList = GetOrCreateSocketGroup(now, out bool newCreate); groupFromScaleList.AddKey(key); _socketToSameTimeKeyGroup.Add(key, groupFromScaleList); if (newCreate) { AdjustTimeout(); } }
2.2 获取超时的socket
时间轴从旧到新,对比群组的时间与超时时刻。就是链表_listTimeScale,从0开始查找。
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/// <summary> ///timeLimit 值为超时时刻限制 ///比如DateTime.Now.AddMilliseconds(-1000);表示 返回一秒钟以前的数据 /// </summary> /// <param name="timeLimit">该时间以前的socket会被返回</param> /// <returns></returns> public List<T> GetTimeoutValue(DateTime timeLimit, bool remove = true) { if((DateTime.Now - timeLimit) > _maxSpan ) { Debug.Write("GetTimeoutSocket timeLimit 参数有误!"); } //从超时组 读取 List<T> result = new List<T>(); foreach(T key in _timeoutSocketGroup) { _timeoutSocketGroup.Add(key); } if(remove) { _timeoutSocketGroup.Clear(); } while (_listTimeScale.Count > 0) { //时间轴从旧到新,查找对比 SameTimeKeyGroup<T> group = _listTimeScale[0]; if(timeLimit >= group.TimeStamp) { foreach (T key in group.KeyGroup) { result.Add(key); if (remove) { _socketToSameTimeKeyGroup.Remove(key); } } if(remove) { _listTimeScale.RemoveAt(0); } } else { break; } } return result; }
3 使用举例
1
2
3
4
5
6
7
8
9
10
11
12//创建变量。最大超时时间为600秒,时间刻度为1秒 TimeSpanManage<Socket> _deviceActiveManage = TimeSpanManage<Socket>.Create(TimeSpan.FromSeconds(600), 1000); //当有数据到达时,调用更新函数 _deviceActiveManage.UpdateTime(socket); //需要在线程或定时器中,每隔一段时间调用,找出超时的socket //找出超时时间超过600秒的socket。 foreach (Socket socket in _deviceActiveManage.GetTimeoutValue(DateTime.Now.AddSeconds(-600))) { socket.Close(); }
4 完整代码
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
2351 /// <summary> 2 /// 超时时间 时间间隔处理 3 /// </summary> 4 class TimeSpanManage<T> 5 { 6 TimeSpan _maxSpan; 7 int _minimumScaleOfMillisecond; 8 int _scaleCount; 9 10 List<SameTimeKeyGroup<T>> _listTimeScale = new List<SameTimeKeyGroup<T>>(); 11 private TimeSpanManage() 12 { 13 } 14 15 /// <summary> 16 /// 17 /// </summary> 18 /// <param name="maxSpan">最大时间时间</param> 19 /// <param name="minimumScaleOfMillisecond">最小刻度(毫秒)</param> 20 /// <returns></returns> 21 public static TimeSpanManage<T> Create(TimeSpan maxSpan, int minimumScaleOfMillisecond) 22 { 23 if (minimumScaleOfMillisecond <= 0) 24 throw new Exception("minimumScaleOfMillisecond 小于0"); 25 if (minimumScaleOfMillisecond > 1000) 26 throw new Exception("minimumScaleOfMillisecond 不能大于1000"); 27 28 if (maxSpan.TotalMilliseconds <= 0) 29 throw new Exception("maxSpan.TotalMilliseconds 小于0"); 30 31 TimeSpanManage<T> result = new TimeSpanManage<T>(); 32 result._maxSpan = maxSpan; 33 result._minimumScaleOfMillisecond = minimumScaleOfMillisecond; 34 35 result._scaleCount = (int)(maxSpan.TotalMilliseconds / minimumScaleOfMillisecond); 36 result._scaleCount++; 37 return result; 38 } 39 40 Dictionary<T, SameTimeKeyGroup<T>> _socketToSameTimeKeyGroup = new Dictionary<T, SameTimeKeyGroup<T>>(); 41 public void UpdateTime(T key) 42 { 43 DateTime now = CreateNow(); 44 //是否已存在,从上一个时间群组删除 45 if (_socketToSameTimeKeyGroup.ContainsKey(key)) 46 { 47 SameTimeKeyGroup<T> group = _socketToSameTimeKeyGroup[key]; 48 if (group.ContainKey(key)) 49 { 50 if (group.TimeStamp == now) //同一时间更新,无需移动 51 { 52 return; 53 } 54 else 55 { 56 group.RemoveKey(key); 57 _socketToSameTimeKeyGroup.Remove(key); 58 } 59 } 60 } 61 62 //从超时组 删除 63 _timeoutSocketGroup.Remove(key); 64 65 //加入到新组 66 SameTimeKeyGroup<T> groupFromScaleList = GetOrCreateSocketGroup(now, out bool newCreate); 67 groupFromScaleList.AddKey(key); 68 69 _socketToSameTimeKeyGroup.Add(key, groupFromScaleList); 70 71 if (newCreate) 72 { 73 AdjustTimeout(); 74 } 75 } 76 77 public bool RemoveSocket(T key) 78 { 79 bool result = false; 80 if (_socketToSameTimeKeyGroup.ContainsKey(key)) 81 { 82 SameTimeKeyGroup<T> group = _socketToSameTimeKeyGroup[key]; 83 result = group.RemoveKey(key); 84 85 _socketToSameTimeKeyGroup.Remove(key); 86 } 87 88 //从超时组 删除 89 bool result2 = _timeoutSocketGroup.Remove(key); 90 return result || result2; 91 } 92 93 /// <summary> 94 ///timeLimit 值为超时时刻限制 95 ///比如DateTime.Now.AddMilliseconds(-1000);表示 返回一秒钟以前的数据 96 /// </summary> 97 /// <param name="timeLimit">该时间以前的socket会被返回</param> 98 /// <returns></returns> 99 public List<T> GetTimeoutValue(DateTime timeLimit, bool remove = true) 100 { 101 if((DateTime.Now - timeLimit) > _maxSpan ) 102 { 103 Debug.Write("GetTimeoutSocket timeLimit 参数有误!"); 104 } 105 106 //从超时组 读取 107 List<T> result = new List<T>(); 108 foreach(T key in _timeoutSocketGroup) 109 { 110 _timeoutSocketGroup.Add(key); 111 } 112 113 if(remove) 114 { 115 _timeoutSocketGroup.Clear(); 116 } 117 118 while (_listTimeScale.Count > 0) 119 { 120 //时间轴从旧到新,查找对比 121 SameTimeKeyGroup<T> group = _listTimeScale[0]; 122 if(timeLimit >= group.TimeStamp) 123 { 124 foreach (T key in group.KeyGroup) 125 { 126 result.Add(key); 127 if (remove) 128 { 129 _socketToSameTimeKeyGroup.Remove(key); 130 } 131 } 132 133 if(remove) 134 { 135 _listTimeScale.RemoveAt(0); 136 } 137 } 138 else 139 { 140 break; 141 } 142 } 143 144 return result; 145 } 146 147 HashSet<T> _timeoutSocketGroup = new HashSet<T>(); 148 private void AdjustTimeout() 149 { 150 while (_listTimeScale.Count > _scaleCount) 151 { 152 SameTimeKeyGroup<T> group = _listTimeScale[0]; 153 foreach (T key in group.KeyGroup) 154 { 155 _timeoutSocketGroup.Add(key); 156 } 157 158 _listTimeScale.RemoveAt(0); 159 } 160 } 161 162 private SameTimeKeyGroup<T> GetOrCreateSocketGroup(DateTime now, out bool newCreate) 163 { 164 if (_listTimeScale.Count == 0) 165 { 166 newCreate = true; 167 SameTimeKeyGroup<T> result = new SameTimeKeyGroup<T>(now); 168 _listTimeScale.Add(result); 169 return result; 170 } 171 else 172 { 173 SameTimeKeyGroup<T> lastGroup = _listTimeScale[_listTimeScale.Count - 1]; 174 if (lastGroup.TimeStamp == now) 175 { 176 newCreate = false; 177 return lastGroup; 178 } 179 180 newCreate = true; 181 SameTimeKeyGroup<T> result = new SameTimeKeyGroup<T>(now); 182 _listTimeScale.Add(result); 183 return result; 184 } 185 } 186 187 DateTime CreateNow() 188 { 189 DateTime now = DateTime.Now; 190 int m = 0; 191 if(now.Millisecond != 0) 192 { 193 if(_minimumScaleOfMillisecond == 1000) 194 { 195 now = now.AddSeconds(1); //尾数加1,确保超时值大于 给定的值 196 } 197 else 198 { 199 //如果now.Millisecond为16毫秒,精确度为10毫秒。则转换后为20毫秒 200 m = now.Millisecond - now.Millisecond % _minimumScaleOfMillisecond + _minimumScaleOfMillisecond; 201 if(m>=1000) 202 { 203 m -= 1000; 204 now = now.AddSeconds(1); 205 } 206 } 207 } 208 return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second,m); 209 } 210 } 211 212 class SameTimeKeyGroup<T> 213 { 214 DateTime _timeStamp; 215 public DateTime TimeStamp => _timeStamp; 216 public SameTimeKeyGroup(DateTime time) 217 { 218 _timeStamp = time; 219 } 220 public HashSet<T> KeyGroup { get; set; } = new HashSet<T>(); 221 222 public bool ContainKey(T key) 223 { 224 return KeyGroup.Contains(key); 225 } 226 227 internal void AddKey(T key) 228 { 229 KeyGroup.Add(key); 230 } 231 internal bool RemoveKey(T key) 232 { 233 return KeyGroup.Remove(key); 234 } 235 }
View Code
最后
以上就是欣喜镜子最近收集整理的关于socket心跳超时检测,快速处理新思路(适用于超大量TCP连接情况下)的全部内容,更多相关socket心跳超时检测内容请搜索靠谱客的其他文章。
发表评论 取消回复