c# socket心跳超时检测的思路(适用于超大量TCP连接情况下)
假设一种情景:
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 完整代码
/// <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 <= 0) throw new exception("minimumscaleofmillisecond 小于0"); if (minimumscaleofmillisecond > 1000) throw new exception("minimumscaleofmillisecond 不能大于1000"); if (maxspan.totalmilliseconds <= 0) 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 > 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; } hashset<t> _timeoutsocketgroup = new hashset<t>(); private void adjusttimeout() { while (_listtimescale.count > _scalecount) { sametimekeygroup<t> group = _listtimescale[0]; foreach (t key in group.keygroup) { _timeoutsocketgroup.add(key); } _listtimescale.removeat(0); } } private sametimekeygroup<t> getorcreatesocketgroup(datetime now, out bool newcreate) { if (_listtimescale.count == 0) { newcreate = true; sametimekeygroup<t> result = new sametimekeygroup<t>(now); _listtimescale.add(result); return result; } else { sametimekeygroup<t> lastgroup = _listtimescale[_listtimescale.count - 1]; 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 = 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); } } 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); } }
以上就是c# socket心跳超时检测的思路(适用于超大量tcp连接情况下)的详细内容,更多关于c# socket心跳超时检测的资料请关注其它相关文章!
上一篇: python程序调用远程服务的步骤详解
下一篇: python 调试器pdb的简单使用