欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

c# socket心跳超时检测的思路(适用于超大量TCP连接情况下)

程序员文章站 2022-04-10 23:34:10
假设一种情景:tcp服务器有1万个客户端连接,如果客户端5秒钟不发数据,则要断开。服务端如何检测客户端是否超时?这看起来是一个非常简单的问题,其实不然!最简单的处理方法是:启动一个线程,每隔一段时间,...

假设一种情景:

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

最简单的处理方法是:

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

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

1 内存布局图

c# socket心跳超时检测的思路(适用于超大量TCP连接情况下)

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

c# socket心跳超时检测的思路(适用于超大量TCP连接情况下)

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心跳超时检测的资料请关注其它相关文章!