假设一种情景: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. 知识点:CSS代码语法

    css 样式由选择符和声明组成,而声明又由属性和值组成,如下图所示: 选择符:又称选择器,指明网页中要应用样式规则的元素,如本例中是网页中所有的段(p)的文字将变成蓝色,而其他的元素(如ol)不会受到 ...

  2. 2017-12-24 自定义view相关学习

    学习材料: http://blog.csdn.net/u010661782/article/details/52805870 http://blog.csdn.net/chengyingzhilian ...

  3. spring之IOC和DI实现

    Spring核心思想 : IOC控制反转 DI依赖注入 AOP切面编程 IOC思想: 场景描述: 在没有加入spring框架之前我们调取业务层时需要创建对象,例如:  接口名   变量名=new  接 ...

  4. How to transfer developer profile to one mac to another mac

    Export developer profile from old mac. In the Xcode Organizer, select your team in the Teams section ...

  5. 如何对CentOS FTP服务配置

    根据很多人对CentOS FTP服务的不解,我觉得应该对CentOS FTP服务做出一定的解释. 1.安装 一般在CentOS上都自动安装了vsftd,若没有安装则可以使用以下步骤进行安装yum -y ...

  6. codeforces891a

    A. Pride time limit per test 2 seconds memory limit per test 256 megabytes input standard input outp ...

  7. 12.DataGrid的columns的特性

  8. kafka eagel的使用

    sql语句eq: select * from "ke_test_topic" where "partition" in (0,1,2) limit 100 官网 ...

  9. 团队博客--测试发布(队名:Running Duck)

    代码链接:Tetris 码云地址 一.Alpha版本测试报告 1.测试计划 测试内容 1.方块生成下落 2.方块左右移动 3.方块快速下滑 4.本汉字替换 5.下一个汉字提示 6.方块颜色生成 7.汉 ...

  10. Asp.Net Core探索 之 appsettings.json

    appsettings.json是什么? 相信大家在.Net Framework的项目都会用的web.config,app.config这些文件,appsettings.json文件就是Asp.Net ...