假设一种情景:TCP服务器有1万个客户端连接,如果客户端5秒钟不发数据,则要断开。服务端如何检测客户端是否超时?这看起来是一个非常简单的问题,其实不然!

最简单的处理方法是:启动一个线程,每隔一段时间,检查每个连接是否超时。每次处理需要1万次检查。计算量太大!检查的时间间隔不能太小,否则大大增加计算量;如果间隔时间太大,超时误差会增大。

本文提出一种新颖的处理方法,就是针对这个看似简单而不易解决的问题!(以下用socket表示一个客户端连接)

 1 内存布局图

  

假设socket3有新的数据到达,需要更新socket3所在的时间轴,处理逻辑如下:

2 处理过程分析:

基本的处理思路就是增加时间轴概念。将socket按最后更新时间排序。因为时间是连续的,不可能将时间分割太细。首先将时间离散,比如属于同一秒内的更新,被认为是属于同一个时间点。离散的时间间隔称为时间刻度,该刻度值可以根据具体情况调整。刻度值越小,超时计算越精确;但是计算量增大。如果时间刻度为10毫秒,则一秒的时间长度被划分为100份。所以需要对更新时间做规整,代码如下:

  1.      DateTime CreateNow()
  2. {
  3. DateTime now = DateTime.Now;
  4. int m = ;
  5. if(now.Millisecond != )
  6. {
  7. if(_minimumScaleOfMillisecond == )
  8. {
  9. now = now.AddSeconds(); //尾数加1,确保超时值大于 给定的值
  10. }
  11. else
  12. {
  13. //如果now.Millisecond为16毫秒,精确度为10毫秒。则转换后为20毫秒
  14. m = now.Millisecond - now.Millisecond % _minimumScaleOfMillisecond + _minimumScaleOfMillisecond;
  15. if(m>=)
  16. {
  17. m -= ;
  18. now = now.AddSeconds();
  19. }
  20. }
  21. }
  22. return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second,m);
  23. }

属于同一个时间刻度的socket,被放入在一个哈希表中(见图中Group)。存放socket的类如下:

  1. class SameTimeKeyGroup<T>
  2. {
  3. DateTime _timeStamp;
  4. public DateTime TimeStamp => _timeStamp;
  5. public SameTimeKeyGroup(DateTime time)
  6. {
  7. _timeStamp = time;
  8. }
  9. public HashSet<T> KeyGroup { get; set; } = new HashSet<T>();
  10.  
  11. public bool ContainKey(T key)
  12. {
  13. return KeyGroup.Contains(key);
  14. }
  15.  
  16. internal void AddKey(T key)
  17. {
  18. KeyGroup.Add(key);
  19. }
  20. internal bool RemoveKey(T key)
  21. {
  22. return KeyGroup.Remove(key);
  23. }
  24. }

定义一个List表示时间轴:

  1. List<SameTimeKeyGroup<T>> _listTimeScale = new List<SameTimeKeyGroup<T>>();

在_listTimeScale 前端的时间较旧,所以链表前端就是有可能超时的socket。

当有socket需要更新时,需要快速知道socket所在的group。这样才能将socket从旧的group移走,再添加到新的group中。需要新增一个链表:

  1. Dictionary<T, SameTimeKeyGroup<T>> _socketToSameTimeKeyGroup = new Dictionary<T, SameTimeKeyGroup<T>>();

 2.1 当socket有新的数据到达时,处理步骤:

  1. 查找socket的上一个群组。如果该群组对应的时刻和当前时刻相同(时间都已经离散,才有可能相同),无需更新时间轴。
  2. 从旧的群组删除,增加到新的群组。
  1.     public void UpdateTime(T key)
  2. {
  3. DateTime now = CreateNow();
  4. //是否已存在,从上一个时间群组删除
  5. if (_socketToSameTimeKeyGroup.ContainsKey(key))
  6. {
  7. SameTimeKeyGroup<T> group = _socketToSameTimeKeyGroup[key];
  8. if (group.ContainKey(key))
  9. {
  10. if (group.TimeStamp == now) //同一时间更新,无需移动
  11. {
  12. return;
  13. }
  14. else
  15. {
  16. group.RemoveKey(key);
  17. _socketToSameTimeKeyGroup.Remove(key);
  18. }
  19. }
  20. }
  21.  
  22. //从超时组 删除
  23. _timeoutSocketGroup.Remove(key);
  24.  
  25. //加入到新组
  26. SameTimeKeyGroup<T> groupFromScaleList = GetOrCreateSocketGroup(now, out bool newCreate);
  27. groupFromScaleList.AddKey(key);
  28.  
  29. _socketToSameTimeKeyGroup.Add(key, groupFromScaleList);
  30.  
  31. if (newCreate)
  32. {
  33. AdjustTimeout();
  34. }
  35. }

 2.2 获取超时的socket

 时间轴从旧到新,对比群组的时间与超时时刻。就是链表_listTimeScale,从0开始查找。

  1. /// <summary>
  2. ///timeLimit 值为超时时刻限制
  3. ///比如DateTime.Now.AddMilliseconds(-1000);表示 返回一秒钟以前的数据
  4. /// </summary>
  5. /// <param name="timeLimit">该时间以前的socket会被返回</param>
  6. /// <returns></returns>
  7. public List<T> GetTimeoutValue(DateTime timeLimit, bool remove = true)
  8. {
  9. if((DateTime.Now - timeLimit) > _maxSpan )
  10. {
  11. Debug.Write("GetTimeoutSocket timeLimit 参数有误!");
  12. }
  13.  
  14. //从超时组 读取
  15. List<T> result = new List<T>();
  16. foreach(T key in _timeoutSocketGroup)
  17. {
  18. _timeoutSocketGroup.Add(key);
  19. }
  20.  
  21. if(remove)
  22. {
  23. _timeoutSocketGroup.Clear();
  24. }
  25.  
  26. while (_listTimeScale.Count > )
  27. {
  28. //时间轴从旧到新,查找对比
  29. SameTimeKeyGroup<T> group = _listTimeScale[];
  30. if(timeLimit >= group.TimeStamp)
  31. {
  32. foreach (T key in group.KeyGroup)
  33. {
  34. result.Add(key);
  35. if (remove)
  36. {
  37. _socketToSameTimeKeyGroup.Remove(key);
  38. }
  39. }
  40.  
  41. if(remove)
  42. {
  43. _listTimeScale.RemoveAt();
  44. }
  45. }
  46. else
  47. {
  48. break;
  49. }
  50. }
  51.  
  52. return result;
  53. }

3 使用举例

  1. //创建变量。最大超时时间为600秒,时间刻度为1秒
  2. TimeSpanManage<Socket> _deviceActiveManage = TimeSpanManage<Socket>.Create(TimeSpan.FromSeconds(), );
  3.  
  4. //当有数据到达时,调用更新函数
  5. _deviceActiveManage.UpdateTime(socket);
  6.  
  7. //需要在线程或定时器中,每隔一段时间调用,找出超时的socket
  8. //找出超时时间超过600秒的socket。
  9. foreach (Socket socket in _deviceActiveManage.GetTimeoutValue(DateTime.Now.AddSeconds(-)))
  10. {
  11. socket.Close();
  12. }

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 <= )
  24. throw new Exception("minimumScaleOfMillisecond 小于0");
  25. if (minimumScaleOfMillisecond > )
  26. throw new Exception("minimumScaleOfMillisecond 不能大于1000");
  27.  
  28. if (maxSpan.TotalMilliseconds <= )
  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 > )
  119. {
  120. //时间轴从旧到新,查找对比
  121. SameTimeKeyGroup<T> group = _listTimeScale[];
  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();
  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[];
  153. foreach (T key in group.KeyGroup)
  154. {
  155. _timeoutSocketGroup.Add(key);
  156. }
  157.  
  158. _listTimeScale.RemoveAt();
  159. }
  160. }
  161.  
  162. private SameTimeKeyGroup<T> GetOrCreateSocketGroup(DateTime now, out bool newCreate)
  163. {
  164. if (_listTimeScale.Count == )
  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 - ];
  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 = ;
  191. if(now.Millisecond != )
  192. {
  193. if(_minimumScaleOfMillisecond == )
  194. {
  195. now = now.AddSeconds(); //尾数加1,确保超时值大于 给定的值
  196. }
  197. else
  198. {
  199. //如果now.Millisecond为16毫秒,精确度为10毫秒。则转换后为20毫秒
  200. m = now.Millisecond - now.Millisecond % _minimumScaleOfMillisecond + _minimumScaleOfMillisecond;
  201. if(m>=)
  202. {
  203. m -= ;
  204. now = now.AddSeconds();
  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. }

socket心跳超时检测,快速处理新思路(适用于超大量TCP连接情况下)的更多相关文章

  1. golang关键字select的三个例子, time.After模拟socket/心跳超时

    golang关键字select的三个例子, time.After模拟socket/心跳超时   例子1 select会随机选择一个可执行的case   // 这个例子主要说明select是随机选择一个 ...

  2. 正常断开连接情况下,判断非阻塞模式socket连接是否断开

    摘自:http://blog.chinaunix.net/uid-15014334-id-3429627.html 在UNIX/LINUX下, 1,对于主动关闭的SOCKET, recv返回-1,而且 ...

  3. 初步探究java中程序退出、GC垃圾回收时,socket tcp连接的行为

    初步探究java中程序退出.GC垃圾回收时,socket tcp连接的行为 今天在项目开发中需要用到socket tcp连接相关(作为tcp客户端),在思考中发觉需要理清socket主动.被动关闭时发 ...

  4. socket心跳检测

    一.什么是心跳检测 判断对方(设备,进程或其它网元)是否正常动行,一般采用定时发送简单的通讯包,如果在指定时间段内未收到对方响应,则判断对方已经当掉.用于检测TCP的异常断开. 基本原因是服务器端不能 ...

  5. [转] Socket心跳包异常检测的C语言实现,服务器与客户端代码案例

    转载自:zxh2075的专栏 在Socket心跳机制中,心跳包可以由服务器发送给客户端,也可以由客户端发送给服务器,不过比较起来,前者开销可能较大.本文实现的是由客户端给服务器发送心跳包,服务器不必返 ...

  6. Socket心跳包异常检测的C语言实现,服务器与客户端代码案例

    在Socket心跳机制中,心跳包可以由服务器发送给客户端,也可以由客户端发送给服务器,不过比较起来,前者开销可能较大.本文实现的是由客户端给服务器发送心跳包,服务器不必返回应答包,而是通过判断客户在线 ...

  7. web socket 心跳包的实现方案

    web socket 心跳包的实现方案05/30/2010 现在网络环境错综复杂,socket心跳包是获得健康强壮的连接的有效解决方案,今天,我们就在web socket中实现心跳包方案,是的,尽管我 ...

  8. socket 心跳包机制

    心跳包的发送,通常有两种技术 方法1:应用层自己实现的心跳包  由应用程序自己发送心跳包来检测连接是否正常,大致的方法是:服务器在一个 Timer事件中定时 向客户端发送一个短小精悍的数据包,然后启动 ...

  9. Socket心跳包机制【转】

    转自:https://blog.csdn.net/xuyuefei1988/article/details/8279812 心跳包的发送,通常有两种技术 方法1:应用层自己实现的心跳包 由应用程序自己 ...

随机推荐

  1. C#时间操作总结

    命名空间:System.Diagnostics Stopwatch 实例可以测量一个时间间隔的运行时间,也可以测量多个时间间隔的总运行时间.在典型的 Stopwatch 方案中,先调用 Start 方 ...

  2. 2.2.10数据类型String的常量池特性

    在JVM中具有String常量池缓存的功能 package com.cky.test; /** * Created by edison on 2017/12/8. */ public class Te ...

  3. Video Processing and Communications:(视频处理和通信)

    https://max.book118.com/html/2017/1010/136711526.shtm Application of (GAN) of AI faceswap in Music V ...

  4. chrome用type=file获取图片路径并转base64字符串

    1 html页面 <div class="col-sm-2" style="height: 200px;margin-top: 14px;"> &l ...

  5. 网络通信协议简介(TCP与UDP)

    通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样.在计算机网络中,这些连接和通信的规则被称为网络通 ...

  6. hdu 5092 线裁剪(纵向连线最小和+输出路径)

    http://acm.hdu.edu.cn/showproblem.php?pid=5092 给一个m*n的矩阵,找到一个纵向的"线"使得线上的和最小并输出这条线,线能向8个方向延 ...

  7. cpld fpga 区别

    cpld fpga 区别 系统的比较,与大家共享: 尽管FPGA和CPLD都是可编程ASIC器件,有很多共同特点,但由于CPLD和FPGA结构上的差异,具有各自的特点: ①CPLD更适合完成各种算法和 ...

  8. 使用dbms_profiler包测试存储过程性能

      原文地址 http://hi.baidu.com/edeed/blog/item/345401e9a8851d38b80e2db4.html dbms_profiler用来测试PL/SQL代码非常 ...

  9. 使用PerfView监测.NET程序性能(一):Event Trace for Windows

    前言: 在日常项目开发中,我们时不时会遇到程序占用了很高CPU的情况,可能是程序里某些未经优化的代码或者Bug,或者是程序运行压力太大.无论是什么原因,我们总希望能看到到底是哪个方法占用了如此高的CP ...

  10. 三、Kubernetes之深入了解Pod

      1.yaml格式的Pod配置文件内容及注解 深入Pod之前,首先我们来了解下Pod的yaml整体文件内容及功能注解. 如下: # yaml格式的pod定义文件完整内容: apiVersion: v ...