1. 程式人生 > >socket心跳超時檢測,快速處理新思路(適用於超大量TCP連線情況下)

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     }

複製程式碼

鄭州市不孕不育醫院

鄭州哪個醫院治療不孕不育好

鄭州不孕不育哪家好

鄭州看不孕不育哪裡好