假设一种情景:

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