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

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

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

 1 内存布局图

  

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

2 处理过程分析:

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

     DateTime CreateNow()
{
DateTime now = DateTime.Now;
int m = ;
if(now.Millisecond != )
{
if(_minimumScaleOfMillisecond == )
{
now = now.AddSeconds(); //尾数加1,确保超时值大于 给定的值
}
else
{
//如果now.Millisecond为16毫秒,精确度为10毫秒。则转换后为20毫秒
m = now.Millisecond - now.Millisecond % _minimumScaleOfMillisecond + _minimumScaleOfMillisecond;
if(m>=)
{
m -= ;
now = now.AddSeconds();
}
}
}
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有新的数据到达时,处理步骤:

  1. 查找socket的上一个群组。如果该群组对应的时刻和当前时刻相同(时间都已经离散,才有可能相同),无需更新时间轴。
  2. 从旧的群组删除,增加到新的群组。
    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 > )
{
//时间轴从旧到新,查找对比
SameTimeKeyGroup<T> group = _listTimeScale[];
if(timeLimit >= group.TimeStamp)
{
foreach (T key in group.KeyGroup)
{
result.Add(key);
if (remove)
{
_socketToSameTimeKeyGroup.Remove(key);
}
} if(remove)
{
_listTimeScale.RemoveAt();
}
}
else
{
break;
}
} return result;
}

3 使用举例

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

4 完整代码

   /// <summary>
/// 超时时间 时间间隔处理
/// </summary>
class TimeSpanManage<T>
{
TimeSpan _maxSpan;
int _minimumScaleOfMillisecond;
int _scaleCount; List<SameTimeKeyGroup<T>> _listTimeScale = new List<SameTimeKeyGroup<T>>();
private TimeSpanManage()
{
} /// <summary>
///
/// </summary>
/// <param name="maxSpan">最大时间时间</param>
/// <param name="minimumScaleOfMillisecond">最小刻度(毫秒)</param>
/// <returns></returns>
public static TimeSpanManage<T> Create(TimeSpan maxSpan, int minimumScaleOfMillisecond)
{
if (minimumScaleOfMillisecond <= )
throw new Exception("minimumScaleOfMillisecond 小于0");
if (minimumScaleOfMillisecond > )
throw new Exception("minimumScaleOfMillisecond 不能大于1000"); if (maxSpan.TotalMilliseconds <= )
throw new Exception("maxSpan.TotalMilliseconds 小于0"); TimeSpanManage<T> result = new TimeSpanManage<T>();
result._maxSpan = maxSpan;
result._minimumScaleOfMillisecond = minimumScaleOfMillisecond; result._scaleCount = (int)(maxSpan.TotalMilliseconds / minimumScaleOfMillisecond);
result._scaleCount++;
return result;
} Dictionary<T, SameTimeKeyGroup<T>> _socketToSameTimeKeyGroup = new Dictionary<T, SameTimeKeyGroup<T>>();
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();
}
} public bool RemoveSocket(T key)
{
bool result = false;
if (_socketToSameTimeKeyGroup.ContainsKey(key))
{
SameTimeKeyGroup<T> group = _socketToSameTimeKeyGroup[key];
result = group.RemoveKey(key); _socketToSameTimeKeyGroup.Remove(key);
} //从超时组 删除
bool result2 = _timeoutSocketGroup.Remove(key);
return result || result2;
} /// <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 > )
{
//时间轴从旧到新,查找对比
SameTimeKeyGroup<T> group = _listTimeScale[];
if(timeLimit >= group.TimeStamp)
{
foreach (T key in group.KeyGroup)
{
result.Add(key);
if (remove)
{
_socketToSameTimeKeyGroup.Remove(key);
}
} if(remove)
{
_listTimeScale.RemoveAt();
}
}
else
{
break;
}
} return result;
} HashSet<T> _timeoutSocketGroup = new HashSet<T>();
private void AdjustTimeout()
{
while (_listTimeScale.Count > _scaleCount)
{
SameTimeKeyGroup<T> group = _listTimeScale[];
foreach (T key in group.KeyGroup)
{
_timeoutSocketGroup.Add(key);
} _listTimeScale.RemoveAt();
}
} private SameTimeKeyGroup<T> GetOrCreateSocketGroup(DateTime now, out bool newCreate)
{
if (_listTimeScale.Count == )
{
newCreate = true;
SameTimeKeyGroup<T> result = new SameTimeKeyGroup<T>(now);
_listTimeScale.Add(result);
return result;
}
else
{
SameTimeKeyGroup<T> lastGroup = _listTimeScale[_listTimeScale.Count - ];
if (lastGroup.TimeStamp == now)
{
newCreate = false;
return lastGroup;
} newCreate = true;
SameTimeKeyGroup<T> result = new SameTimeKeyGroup<T>(now);
_listTimeScale.Add(result);
return result;
}
} DateTime CreateNow()
{
DateTime now = DateTime.Now;
int m = ;
if(now.Millisecond != )
{
if(_minimumScaleOfMillisecond == )
{
now = now.AddSeconds(); //尾数加1,确保超时值大于 给定的值
}
else
{
//如果now.Millisecond为16毫秒,精确度为10毫秒。则转换后为20毫秒
m = now.Millisecond - now.Millisecond % _minimumScaleOfMillisecond + _minimumScaleOfMillisecond;
if(m>=)
{
m -= ;
now = now.AddSeconds();
}
}
}
return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second,m);
}
} 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);
}
}

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. 2.2.1synchronized方法的弊端

    缺陷:用关键字synchronized声明方法是有弊端的,譬如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待较长的时间, 解决方法:使用synchronized同步语句块 package ...

  2. MIT molecular Biology 笔记11 位点特异性重组 和 DNA转座

    位点特异性重组 和 DNA转座 视频 https://www.bilibili.com/video/av7973580/ 教材 Molecular biology of the gene 7th ed ...

  3. day27(反射之内省机制)

    内省 内省:底层是使用反射机制实现的,是对于反射的进一步封装. 反射:通过类名来获取类中的所有属性和方法及类中的所有隐藏的方法. 内省:通过一个标准类(javabean类)来获取bean中的字段.ge ...

  4. (单调队列) Bad Hair Day -- POJ -- 3250

    http://poj.org/problem?id=3250 Bad Hair Day Time Limit: 2000MS   Memory Limit: 65536K Total Submissi ...

  5. noip第1课作业

    1.    求三个数的乘积和三次方和 [问题描述] 编程实现输入任意三个整数a, b, c,将这三个数的乘积以及三次方和输出: [样例输入] 1 2 3 [样例输出] 6 36 #include &l ...

  6. java 调用c++程序实例

    1.java程序: package com.zhangshitong; import java.io.File; public class Java2cpp { static{ System.load ...

  7. less(css)语言快速入门

    转载 原文地址:https://www.ibm.com/developerworks/cn/web/1207_zhaoch_lesscss/ 简介 CSS(层叠样式表)是一门历史悠久的标记性语言,同 ...

  8. [mysql语句] mysql 语句收集

    // http://stackoverflow.com/questions/6666152/mysql-order-by-where 1. "select * from t_activity ...

  9. HTTP状态代码列表

    httpContext.Response.StatusCode=200 1xx - 信息提示这些状态代码表示临时的响应.客户端在收到常规响应之前,应准备接收一个或多个 1xx 响应. · 100 - ...

  10. 【PHP】当mysql遇上PHP

    博客提纲 利用PHP连接mySQL数据库 两套接口:面向对象和面向过程 实现写改删查(CUBD)实例 通过prepare语句处理相同类型的不同SQL语句 通过bind_param()绑定参数,及相关注 ...