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

编辑: admin 分类: c#语言 发布时间: 2021-11-25 来源:互联网
目录
  • 假设一种情景:
  • 最简单的处理方法是:
  • 1 内存布局图
  • 2 处理过程分析:
    • 2.1 当socket有新的数据到达时,处理步骤:
    • 2.2 获取超时的socket
  • 3 使用举例
    • 4 完整代码

      假设一种情景:

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

      【文章来自:高防ip处的文章,转载请说明出处】