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

(0)

相关推荐

  • C#使用Socket实现服务器与多个客户端通信(简单的聊天系统)

    扩展: 由于server端是存储了所有server与client的连接对象,因此我们是可以基于此demo的基础上实现聊天系统: * 每当一个与用户发言时,是由server接收到的某个用户的发言信息的,此时服务器端可以通过循环发送该用户发送的信息给每个已经连接连接的用户(排除发送者). Server端代码: class Program { //创建一个和客户端通信的套接字 static Socket SocketWatch = null; //定义一个集合,存储客户端信息 static Dicti

  • C# 实现Scoket心跳机制的方法

    TCP网络长连接 手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接.TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在"无差别"的网络之上. 建立起一个TCP连接需要经过"三次握手": 第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认: 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+

  • 利用C#实现SSLSocket加密通讯的方法详解

    前言 SSL Socket通讯是对socket的扩展,增加Socket通讯的数据安全性,SSL认证分为单向和双向认证.单向认证只认证服务器端的合法性而不认证客户端的合法性.双向认证是同时认证服务端和客户端.下面我分别说说使用C#实现单向认证和双向认证的过程,并用代码实现. 一. 单向认证 第1步:准备一个数字证书,可以使用如下脚本生成 先进入到vs2005的命令行状态,即: 开始–>程序–>Microsoft Visual Studio 2005–>Visual Studio Tools

  • C#使用Socket实现心跳的方法示例

    Server端代码: class Program { static SocketListener listener; public static void Main(string[] args) { //实例化Timer类,设置间隔时间为5000毫秒: System.Timers.Timer t = new System.Timers.Timer(5000); t.Elapsed += new System.Timers.ElapsedEventHandler(CheckListen); //到

  • C# 通过Socket读取大量数据的示例

    在C#中经常会用到Socket去接收和发送数据,而且也是非常方便的,有时候我们会向服务端去请求数据,如果返回的数据量很大,比如超过10M甚至是更多,那么该怎样去接收数据呢?下面以一个在项目中用到的实例去分析和解释这个问题,先看看下面的这段代码? /// <summary>         /// 返回摄像头信息         /// </summary>         private void RcvCameraInfos(object obj)         {      

  • SuperSocket封装成C#类库的步骤

    将SuperSocket封装成类库之后可以将其集成进各种类型的应用,而不仅仅局限于控制台应用程序了,从而应用于不同的场景.这里以TelnetServer为例说明如何进行操作. 首先,创建一个C#类库项目LibSocketServer 添加SuperSocket引用(SuperSocket.Common.dll,SuperSocket.SocketBase.dll,SuperSocket.SocketEngine.dll),添加默认的日志框架log4net.dll引用.将log4net.confi

  • C#SuperSocket的搭建并配置启动总结

    之前我们借助一个SuperSocket实现了一个简易版的服务器, 但是不管是Server还是Session都是使用框架的,本篇博客我们要实现自己的Server和Session,来重写框架原生的Server或Session的方法,或添加自己所需的属性,来实现自己的业务逻辑,并且也不在使用事件来绑定接收,连接,或关闭事件,全部交给Bootstrap来执行,(这个Bootstrap并不是指前端框架的Bootstrap ,而是指的SuperSocket框架的一个引导程序或说是辅助程序),就是这里我们会使

  • C# 实现WebSocket服务端教程

    .net4.5中实现了对websocket的支持 在这里我使用的是.net4.0.因此需要对原本的socket发送的数据根据websocket的协议进行解析和打包. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net.Sockets; using System.Threading; using System.Net; namespace We

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

    假设一种情景: TCP服务器有1万个客户端连接,如果客户端5秒钟不发数据,则要断开.服务端如何检测客户端是否超时?这看起来是一个非常简单的问题,其实不然! 最简单的处理方法是: 启动一个线程,每隔一段时间,检查每个连接是否超时.每次处理需要1万次检查.计算量太大!检查的时间间隔不能太小,否则大大增加计算量:如果间隔时间太大,超时误差会增大. 本文提出一种新颖的处理方法,就是针对这个看似简单而不易解决的问题!(以下用socket表示一个客户端连接) 1 内存布局图 假设socket3有新的数据到达

  • php登录超时检测功能实例详解

    php登录超时检测功能实例详解 前言: php登录超时问题,当用户超过一定时间没有操作页面时自动退出登录,原理是通过js进行访问判断的!代码如下(以thinkphp5.0版本为例) 1.创建登录版块控制器: <?php namespace app\manage\control; use \think\Controller; class Main extends Controller{ protected $request; public function _initialize(){ $this

  • C#中一个高性能异步socket封装库的实现思路分享

    前言 socket是软件之间通讯最常用的一种方式.c#实现socket通讯有很多中方法,其中效率最高就是异步通讯. 异步通讯实际是利用windows完成端口(IOCP)来处理的,关于完成端口实现原理,大家可以参考网上文章. 我这里想强调的是采用完成端口机制的异步通讯是windows下效率最高的通讯方式,没有之一! 异步通讯比同步通讯处理要难很多,代码编写中会遇到许多"坑".如果没有经验,很难完成. 我搜集了大量资料,完成了对异步socket的封装.此库已用稳定高效的运行几个月. 纵观网

  • go语言心跳超时的实现示例

    目录 一.背景 二.心跳超时的实现 2.1 通过select case (设计概念比较多) 2.2 通过time.sleep(简单有效) 三.个人的实现观感 一.背景 本文描述的是客户端接收心跳信息的超时实现.心跳超时,或者接受信息超过限定时间在分布式系统中出现的次数比较多.常见的就有hadoop中节点超时,或者日志中出现timeout的字样. 在学习go语言中,我也根据go语言的机制实现了心跳超时的这个问题.踩过坑,趟过水. 二.心跳超时的实现 2.1 通过select case (设计概念比

  • Android使用socket创建简单TCP连接的方法

    本文实例讲述了Android使用socket创建简单TCP连接的方法.分享给大家供大家参考,具体如下: 不管是在Java还是Android编程中,通信都是及其重要的一部分.有连接的socket编程,重要性自然毋庸置疑. 这里以一个简单的demo演示一个最基本的socket编程. 先写服务端.服务端是Java代码.笔者懒得装eclipse等编程软件,就是直接notepad编程,dos运行的.服务端一般是新建一个绑定端口的serversocket,监听客户端请求(死循环监听).当接收到客户端消息时,

  • 常用类之TCP连接类-socket编程

    tcp一般用于维持一个可信任的连接,比起udp更为安全可靠,在vs.net,分别有tcpclient和udpclient以及tcplistener,一般开发中基本可以满足需要,但是这个有个很大的弊端,对于维持一个时间较长的,相互交互的来说,数据处理不是很明朗,vs/net中还有一个socket类,用他来做一个客户/服务器段,同时在接发数据的时候,能相互独立,这需要一个异步通讯过程 先实现服务器段: using System; using System.Net; using System.Net.

  • jquery实现适用于门户站的导航下拉菜单效果代码

    本文实例讲述了jquery实现适用于门户站的导航下拉菜单效果代码.分享给大家供大家参考.具体如下: 这是一款应用了jQuery+CSS共同编写实现的网页导航菜单,采用浅蓝色的布局方式,整体格调淡雅清新,简洁大方,不失为一款好菜单.本特点的特点是兼容好,在火狐/IE下都有良好表现,而且在有二级子菜单的地方,主菜单上都会有一个小三角标识,很好的提升了用户体验,相信你也会喜欢的. 运行效果截图如下: 在线演示地址如下: http://demo.jb51.net/js/2015/jquery-web-i

  • IIS7.5 检测到在集成的托管管道模式下不适用的 ASP.NET设置

    在调试一个网上下载的.net网站时,打开就提示出错: 错误摘要 HTTP 错误 500.22 - Internal Server Error 检测到在集成的托管管道模式下不适用的 ASP.NET 设置. 根据提示错误,应该是需要把应用程序池的托管管道模式从[集成]改为[经典], 打开IIS管理器,点击左上角的[应用程序池],在[应用程序池]里面找到该网站的应用程序池名称,双击打开基本设置,把托管管道模式改为[经典]. 这是网站使用独立应用程序池的更改方法,如果是共享一个默认的应用程序池,应该是修

  • 检测ip和port是否可连接的方法

    windows cmd telnet format: telnet ip port case: telnet 191.1.1.1 8080 telnet不能加http:// telnet不支持域名 telnet默认端口是23 telnet连接ip和port是空格" "而不是冒号":" 如果不可连接,会提示连接失败,否则如果cmd出现一大段空白和不可操作界面则表示连接成功. ping format: ping ip或者domain:port case: ping 19

  • Python socket如何实现服务端和客户端数据传输(TCP)

    目录 socket服务端和客户端数据传输(TCP) socket服务端,客户端互相通信 socket服务端和客户端数据传输(TCP) 服务器端: import socket #创建一个socket对象 socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = "127.0.0.1" port = 9999 #绑定地址 socket_server.bind((host, port)) #设置监听 so

随机推荐