c# socket心跳超时检测的思路(适用于超大量TCP连接情况下)
假设一种情景:
TCP服务器有1万个客户端连接,如果客户端5秒钟不发数据,则要断开。服务端如何检测客户端是否超时?这看起来是一个非常简单的问题,其实不然!
最简单的处理方法是:
启动一个线程,每隔一段时间,检查每个连接是否超时。每次处理需要1万次检查。计算量太大!检查的时间间隔不能太小,否则大大增加计算量;如果间隔时间太大,超时误差会增大。
本文提出一种新颖的处理方法,就是针对这个看似简单而不易解决的问题!(以下用socket表示一个客户端连接)
1内存布局图
假设socket3有新的数据到达,需要更新socket3所在的时间轴,处理逻辑如下:
2处理过程分析:
基本的处理思路就是增加时间轴概念。将socket按最后更新时间排序。因为时间是连续的,不可能将时间分割太细。首先将时间离散,比如属于同一秒内的更新,被认为是属于同一个时间点。离散的时间间隔称为时间刻度,该刻度值可以根据具体情况调整。刻度值越小,超时计算越精确;但是计算量增大。如果时间刻度为10毫秒,则一秒的时间长度被划分为100份。所以需要对更新时间做规整,代码如下:
DateTimeCreateNow() { DateTimenow=DateTime.Now; intm=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); } } } returnnewDateTime(now.Year,now.Month,now.Day,now.Hour,now.Minute,now.Second,m); }
属于同一个时间刻度的socket,被放入在一个哈希表中(见图中Group)。存放socket的类如下:
classSameTimeKeyGroup{ DateTime_timeStamp; publicDateTimeTimeStamp=>_timeStamp; publicSameTimeKeyGroup(DateTimetime) { _timeStamp=time; } publicHashSet KeyGroup{get;set;}=newHashSet (); publicboolContainKey(Tkey) { returnKeyGroup.Contains(key); } internalvoidAddKey(Tkey) { KeyGroup.Add(key); } internalboolRemoveKey(Tkey) { returnKeyGroup.Remove(key); } }
定义一个List表示时间轴:
List>_listTimeScale=newList >();
在_listTimeScale前端的时间较旧,所以链表前端就是有可能超时的socket。
当有socket需要更新时,需要快速知道socket所在的group。这样才能将socket从旧的group移走,再添加到新的group中。需要新增一个链表:
Dictionary>_socketToSameTimeKeyGroup=newDictionary >();
2.1当socket有新的数据到达时,处理步骤:
- 查找socket的上一个群组。如果该群组对应的时刻和当前时刻相同(时间都已经离散,才有可能相同),无需更新时间轴。
- 从旧的群组删除,增加到新的群组。
publicvoidUpdateTime(Tkey) { DateTimenow=CreateNow(); //是否已存在,从上一个时间群组删除 if(_socketToSameTimeKeyGroup.ContainsKey(key)) { SameTimeKeyGroupgroup=_socketToSameTimeKeyGroup[key]; if(group.ContainKey(key)) { if(group.TimeStamp==now)//同一时间更新,无需移动 { return; } else { group.RemoveKey(key); _socketToSameTimeKeyGroup.Remove(key); } } } //从超时组删除 _timeoutSocketGroup.Remove(key); //加入到新组 SameTimeKeyGroup groupFromScaleList=GetOrCreateSocketGroup(now,outboolnewCreate); groupFromScaleList.AddKey(key); _socketToSameTimeKeyGroup.Add(key,groupFromScaleList); if(newCreate) { AdjustTimeout(); } }
2.2获取超时的socket
时间轴从旧到新,对比群组的时间与超时时刻。就是链表_listTimeScale,从0开始查找。
//////timeLimit值为超时时刻限制 ///比如DateTime.Now.AddMilliseconds(-1000);表示返回一秒钟以前的数据 /// ///该时间以前的socket会被返回 /// publicList GetTimeoutValue(DateTimetimeLimit,boolremove=true) { if((DateTime.Now-timeLimit)>_maxSpan) { Debug.Write("GetTimeoutSockettimeLimit参数有误!"); } //从超时组读取 List result=newList (); foreach(Tkeyin_timeoutSocketGroup) { _timeoutSocketGroup.Add(key); } if(remove) { _timeoutSocketGroup.Clear(); } while(_listTimeScale.Count>0) { //时间轴从旧到新,查找对比 SameTimeKeyGroup group=_listTimeScale[0]; if(timeLimit>=group.TimeStamp) { foreach(Tkeyingroup.KeyGroup) { result.Add(key); if(remove) { _socketToSameTimeKeyGroup.Remove(key); } } if(remove) { _listTimeScale.RemoveAt(0); } } else { break; } } returnresult; }
3使用举例
//创建变量。最大超时时间为600秒,时间刻度为1秒 TimeSpanManage_deviceActiveManage=TimeSpanManage .Create(TimeSpan.FromSeconds(600),1000); //当有数据到达时,调用更新函数 _deviceActiveManage.UpdateTime(socket); //需要在线程或定时器中,每隔一段时间调用,找出超时的socket //找出超时时间超过600秒的socket。 foreach(Socketsocketin_deviceActiveManage.GetTimeoutValue(DateTime.Now.AddSeconds(-600))) { socket.Close(); }
4完整代码
//////超时时间时间间隔处理 /// classTimeSpanManage{ TimeSpan_maxSpan; int_minimumScaleOfMillisecond; int_scaleCount; List >_listTimeScale=newList >(); privateTimeSpanManage() { } /// /// /// ///最大时间时间 /// 最小刻度(毫秒) /// publicstaticTimeSpanManage Create(TimeSpanmaxSpan,intminimumScaleOfMillisecond) { if(minimumScaleOfMillisecond<=0) thrownewException("minimumScaleOfMillisecond小于0"); if(minimumScaleOfMillisecond>1000) thrownewException("minimumScaleOfMillisecond不能大于1000"); if(maxSpan.TotalMilliseconds<=0) thrownewException("maxSpan.TotalMilliseconds小于0"); TimeSpanManage result=newTimeSpanManage (); result._maxSpan=maxSpan; result._minimumScaleOfMillisecond=minimumScaleOfMillisecond; result._scaleCount=(int)(maxSpan.TotalMilliseconds/minimumScaleOfMillisecond); result._scaleCount++; returnresult; } Dictionary >_socketToSameTimeKeyGroup=newDictionary >(); publicvoidUpdateTime(Tkey) { DateTimenow=CreateNow(); //是否已存在,从上一个时间群组删除 if(_socketToSameTimeKeyGroup.ContainsKey(key)) { SameTimeKeyGroup group=_socketToSameTimeKeyGroup[key]; if(group.ContainKey(key)) { if(group.TimeStamp==now)//同一时间更新,无需移动 { return; } else { group.RemoveKey(key); _socketToSameTimeKeyGroup.Remove(key); } } } //从超时组删除 _timeoutSocketGroup.Remove(key); //加入到新组 SameTimeKeyGroup groupFromScaleList=GetOrCreateSocketGroup(now,outboolnewCreate); groupFromScaleList.AddKey(key); _socketToSameTimeKeyGroup.Add(key,groupFromScaleList); if(newCreate) { AdjustTimeout(); } } publicboolRemoveSocket(Tkey) { boolresult=false; if(_socketToSameTimeKeyGroup.ContainsKey(key)) { SameTimeKeyGroup group=_socketToSameTimeKeyGroup[key]; result=group.RemoveKey(key); _socketToSameTimeKeyGroup.Remove(key); } //从超时组删除 boolresult2=_timeoutSocketGroup.Remove(key); returnresult||result2; } /// ///timeLimit值为超时时刻限制 ///比如DateTime.Now.AddMilliseconds(-1000);表示返回一秒钟以前的数据 /// ///该时间以前的socket会被返回 /// publicList GetTimeoutValue(DateTimetimeLimit,boolremove=true) { if((DateTime.Now-timeLimit)>_maxSpan) { Debug.Write("GetTimeoutSockettimeLimit参数有误!"); } //从超时组读取 List result=newList (); foreach(Tkeyin_timeoutSocketGroup) { _timeoutSocketGroup.Add(key); } if(remove) { _timeoutSocketGroup.Clear(); } while(_listTimeScale.Count>0) { //时间轴从旧到新,查找对比 SameTimeKeyGroup group=_listTimeScale[0]; if(timeLimit>=group.TimeStamp) { foreach(Tkeyingroup.KeyGroup) { result.Add(key); if(remove) { _socketToSameTimeKeyGroup.Remove(key); } } if(remove) { _listTimeScale.RemoveAt(0); } } else { break; } } returnresult; } HashSet _timeoutSocketGroup=newHashSet (); privatevoidAdjustTimeout() { while(_listTimeScale.Count>_scaleCount) { SameTimeKeyGroup group=_listTimeScale[0]; foreach(Tkeyingroup.KeyGroup) { _timeoutSocketGroup.Add(key); } _listTimeScale.RemoveAt(0); } } privateSameTimeKeyGroup GetOrCreateSocketGroup(DateTimenow,outboolnewCreate) { if(_listTimeScale.Count==0) { newCreate=true; SameTimeKeyGroup result=newSameTimeKeyGroup (now); _listTimeScale.Add(result); returnresult; } else { SameTimeKeyGroup lastGroup=_listTimeScale[_listTimeScale.Count-1]; if(lastGroup.TimeStamp==now) { newCreate=false; returnlastGroup; } newCreate=true; SameTimeKeyGroup result=newSameTimeKeyGroup (now); _listTimeScale.Add(result); returnresult; } } DateTimeCreateNow() { DateTimenow=DateTime.Now; intm=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); } } } returnnewDateTime(now.Year,now.Month,now.Day,now.Hour,now.Minute,now.Second,m); } } classSameTimeKeyGroup { DateTime_timeStamp; publicDateTimeTimeStamp=>_timeStamp; publicSameTimeKeyGroup(DateTimetime) { _timeStamp=time; } publicHashSet KeyGroup{get;set;}=newHashSet (); publicboolContainKey(Tkey) { returnKeyGroup.Contains(key); } internalvoidAddKey(Tkey) { KeyGroup.Add(key); } internalboolRemoveKey(Tkey) { returnKeyGroup.Remove(key); } }
以上就是c#socket心跳超时检测的思路(适用于超大量TCP连接情况下)的详细内容,更多关于c#socket心跳超时检测的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。