概述
假设一种情景:TCP服务器有1万个客户端连接,如果客户端5秒钟不发数据,则要断开。服务端如何检测客户端是否超时?这看起来是一个非常简单的问题,其实不然!
最简单的处理方法是:启动一个线程,每隔一段时间,检查每个连接是否超时。每次处理需要1万次检查。计算量太大!检查的时间间隔不能太小,否则大大增加计算量;如果间隔时间太大,超时误差会增大。
本文提出一种新颖的处理方法,就是针对这个看似简单而不易解决的问题!(以下用socket表示一个客户端连接)
1 内存布局图
假设socket3有新的数据到达,需要更新socket3所在的时间轴,处理逻辑如下:
2 处理过程分析:
基本的处理思路就是增加时间轴概念。将socket按最后更新时间排序。因为时间是连续的,不可能将时间分割太细。首先将时间离散,比如属于同一秒内的更新,被认为是属于同一个时间点。离散的时间间隔称为时间刻度,该刻度值可以根据具体情况调整。刻度值越小,超时计算越精确;但是计算量增大。如果时间刻度为10毫秒,则一秒的时间长度被划分为100份。所以需要对更新时间做规整,代码如下:
DateTime 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的类如下:
class 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表示时间轴:
List<SameTimeKeyGroup<T>> _listTimeScale = new List<SameTimeKeyGroup<T>>();
在_listTimeScale 前端的时间较旧,所以链表前端就是有可能超时的socket。
当有socket需要更新时,需要快速知道socket所在的group。这样才能将socket从旧的group移走,再添加到新的group中。需要新增一个链表:
Dictionary<T, SameTimeKeyGroup<T>> _socketToSameTimeKeyGroup = new Dictionary<T, SameTimeKeyGroup<T>>();
2.1 当socket有新的数据到达时,处理步骤:
- 查找socket的上一个群组。如果该群组对应的时刻和当前时刻相同(时间都已经离散,才有可能相同),无需更新时间轴。
- 从旧的群组删除,增加到新的群组。
public 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开始查找。
/// <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 使用举例
//创建变量。最大超时时间为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 /// <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心跳超时检测,快速处理新思路(适用于超大量TCP连接情况下)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复