• 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有新的数据到达时,处理步骤:

    1.  查找socket的上一个群组。如果该群组对应的时刻和当前时刻相同(时间都已经离散,才有可能相同),无需更新时间轴。
    2. 从旧的群组删除,增加到新的群组。
        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 完整代码

      1   /// <summary>
      2     /// 超时时间 时间间隔处理
      3     /// </summary>
      4     class TimeSpanManage<T>
      5     {
      6         TimeSpan _maxSpan;
      7         int _minimumScaleOfMillisecond;
      8         int _scaleCount;
      9 
     10         List<SameTimeKeyGroup<T>> _listTimeScale = new List<SameTimeKeyGroup<T>>();
     11         private TimeSpanManage()
     12         {
     13         }
     14 
     15         /// <summary>
     16         /// 
     17         /// </summary>
     18         /// <param name="maxSpan">最大时间时间</param>
     19         /// <param name="minimumScaleOfMillisecond">最小刻度(毫秒)</param>
     20         /// <returns></returns>
     21         public static TimeSpanManage<T> Create(TimeSpan maxSpan, int minimumScaleOfMillisecond)
     22         {
     23             if (minimumScaleOfMillisecond <= 0)
     24                 throw new Exception("minimumScaleOfMillisecond 小于0");
     25             if (minimumScaleOfMillisecond > 1000)
     26                 throw new Exception("minimumScaleOfMillisecond 不能大于1000");
     27 
     28             if (maxSpan.TotalMilliseconds <= 0)
     29                 throw new Exception("maxSpan.TotalMilliseconds 小于0");
     30 
     31             TimeSpanManage<T> result = new TimeSpanManage<T>();
     32             result._maxSpan = maxSpan;
     33             result._minimumScaleOfMillisecond = minimumScaleOfMillisecond;
     34 
     35             result._scaleCount = (int)(maxSpan.TotalMilliseconds / minimumScaleOfMillisecond);
     36             result._scaleCount++;
     37             return result;
     38         }
     39 
     40         Dictionary<T, SameTimeKeyGroup<T>> _socketToSameTimeKeyGroup = new Dictionary<T, SameTimeKeyGroup<T>>();
     41         public void UpdateTime(T key)
     42         {
     43             DateTime now = CreateNow();
     44             //是否已存在,从上一个时间群组删除
     45             if (_socketToSameTimeKeyGroup.ContainsKey(key))
     46             {
     47                 SameTimeKeyGroup<T> group = _socketToSameTimeKeyGroup[key];
     48                 if (group.ContainKey(key))
     49                 {
     50                     if (group.TimeStamp == now) //同一时间更新,无需移动
     51                     {
     52                         return;
     53                     }
     54                     else
     55                     {
     56                         group.RemoveKey(key);
     57                         _socketToSameTimeKeyGroup.Remove(key);
     58                     }
     59                 }
     60             }
     61 
     62             //从超时组 删除
     63             _timeoutSocketGroup.Remove(key);
     64 
     65             //加入到新组
     66             SameTimeKeyGroup<T> groupFromScaleList = GetOrCreateSocketGroup(now, out bool newCreate);
     67             groupFromScaleList.AddKey(key);
     68 
     69             _socketToSameTimeKeyGroup.Add(key, groupFromScaleList);
     70 
     71             if (newCreate)
     72             {
     73                 AdjustTimeout();
     74             }
     75         }
     76 
     77         public bool RemoveSocket(T key)
     78         {
     79             bool result = false;
     80             if (_socketToSameTimeKeyGroup.ContainsKey(key))
     81             {
     82                 SameTimeKeyGroup<T> group = _socketToSameTimeKeyGroup[key];
     83                 result = group.RemoveKey(key);
     84 
     85                 _socketToSameTimeKeyGroup.Remove(key);
     86             }
     87 
     88             //从超时组 删除
     89             bool result2 = _timeoutSocketGroup.Remove(key);
     90             return result || result2;
     91         }
     92 
     93         /// <summary>
     94         ///timeLimit 值为超时时刻限制 
     95         ///比如DateTime.Now.AddMilliseconds(-1000);表示 返回一秒钟以前的数据
     96         /// </summary>
     97         /// <param name="timeLimit">该时间以前的socket会被返回</param>
     98         /// <returns></returns>
     99         public List<T> GetTimeoutValue(DateTime timeLimit, bool remove = true)
    100         {
    101             if((DateTime.Now - timeLimit) > _maxSpan )
    102             {
    103                 Debug.Write("GetTimeoutSocket timeLimit 参数有误!");
    104             }
    105 
    106             //从超时组 读取
    107             List<T> result = new List<T>();
    108             foreach(T key in _timeoutSocketGroup)
    109             {
    110                 _timeoutSocketGroup.Add(key);
    111             }
    112 
    113             if(remove)
    114             {
    115                 _timeoutSocketGroup.Clear();
    116             }
    117 
    118             while (_listTimeScale.Count > 0)
    119             {
    120                 //时间轴从旧到新,查找对比
    121                 SameTimeKeyGroup<T> group = _listTimeScale[0];
    122                 if(timeLimit >= group.TimeStamp)
    123                 {
    124                     foreach (T key in group.KeyGroup)
    125                     {
    126                         result.Add(key);
    127                         if (remove)
    128                         {
    129                             _socketToSameTimeKeyGroup.Remove(key);
    130                         }
    131                     }
    132 
    133                     if(remove)
    134                     {
    135                         _listTimeScale.RemoveAt(0);
    136                     }
    137                 }
    138                 else
    139                 {
    140                     break;
    141                 }
    142             }
    143 
    144             return result;
    145         }
    146 
    147         HashSet<T> _timeoutSocketGroup = new HashSet<T>();
    148         private void AdjustTimeout()
    149         {
    150             while (_listTimeScale.Count > _scaleCount)
    151             {
    152                 SameTimeKeyGroup<T> group = _listTimeScale[0];
    153                 foreach (T key in group.KeyGroup)
    154                 {
    155                     _timeoutSocketGroup.Add(key);
    156                 }
    157 
    158                 _listTimeScale.RemoveAt(0);
    159             }
    160         }
    161 
    162         private SameTimeKeyGroup<T> GetOrCreateSocketGroup(DateTime now, out bool newCreate)
    163         {
    164             if (_listTimeScale.Count == 0)
    165             {
    166                 newCreate = true;
    167                 SameTimeKeyGroup<T> result = new SameTimeKeyGroup<T>(now);
    168                 _listTimeScale.Add(result);
    169                 return result;
    170             }
    171             else
    172             {
    173                 SameTimeKeyGroup<T> lastGroup = _listTimeScale[_listTimeScale.Count - 1];
    174                 if (lastGroup.TimeStamp == now)
    175                 {
    176                     newCreate = false;
    177                     return lastGroup;
    178                 }
    179 
    180                 newCreate = true;
    181                 SameTimeKeyGroup<T> result = new SameTimeKeyGroup<T>(now);
    182                 _listTimeScale.Add(result);
    183                 return result;
    184             }
    185         }
    186 
    187         DateTime CreateNow()
    188         {
    189             DateTime now = DateTime.Now;
    190             int m = 0; 
    191             if(now.Millisecond != 0)
    192             {
    193                 if(_minimumScaleOfMillisecond == 1000)
    194                 {
    195                     now = now.AddSeconds(1); //尾数加1,确保超时值大于 给定的值
    196                 }
    197                 else
    198                 {
    199                     //如果now.Millisecond为16毫秒,精确度为10毫秒。则转换后为20毫秒
    200                     m = now.Millisecond - now.Millisecond % _minimumScaleOfMillisecond + _minimumScaleOfMillisecond;
    201                     if(m>=1000)
    202                     {
    203                         m -= 1000;
    204                         now = now.AddSeconds(1);
    205                     }
    206                 }
    207             }
    208             return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second,m);
    209         }
    210     }
    211 
    212     class SameTimeKeyGroup<T>
    213     {
    214         DateTime _timeStamp;
    215         public DateTime TimeStamp => _timeStamp;
    216         public SameTimeKeyGroup(DateTime time)
    217         {
    218             _timeStamp = time;
    219         }
    220         public HashSet<T> KeyGroup { get; set; } = new HashSet<T>();
    221 
    222         public bool ContainKey(T key)
    223         {
    224             return KeyGroup.Contains(key);
    225         }
    226 
    227         internal void AddKey(T key)
    228         {
    229             KeyGroup.Add(key);
    230         }
    231         internal bool RemoveKey(T key)
    232         {
    233            return  KeyGroup.Remove(key);
    234         }
    235     }
    View Code
  • 相关阅读:
    MT7688 Ubuntu uboot编译报错问题
    Ubuntu安装FTP服务器
    普罗米修斯
    【笔记】redis实现类
    问题记录:'AxesSubplot' object does not support indexing
    matplotlib解决子图重叠问题:plt.tight_layout()
    JavaBean转Json,null值忽略问题
    Lombok @SneakyThrows注解
    SpringBoot整合logback
    [转]SpringBoot 生产中 16 条最佳实践
  • 原文地址:https://www.cnblogs.com/yuanchenhui/p/socket_active_check.html
Copyright © 2020-2023  润新知