c# socket心跳超時(shí)檢測(cè)的思路(適用于超大量TCP連接情況下)
假設(shè)一種情景:
TCP服務(wù)器有1萬(wàn)個(gè)客戶(hù)端連接,如果客戶(hù)端5秒鐘不發(fā)數(shù)據(jù),則要斷開(kāi)。服務(wù)端如何檢測(cè)客戶(hù)端是否超時(shí)?這看起來(lái)是一個(gè)非常簡(jiǎn)單的問(wèn)題,其實(shí)不然!
最簡(jiǎn)單的處理方法是:
啟動(dòng)一個(gè)線(xiàn)程,每隔一段時(shí)間,檢查每個(gè)連接是否超時(shí)。每次處理需要1萬(wàn)次檢查。計(jì)算量太大!檢查的時(shí)間間隔不能太小,否則大大增加計(jì)算量;如果間隔時(shí)間太大,超時(shí)誤差會(huì)增大。
本文提出一種新穎的處理方法,就是針對(duì)這個(gè)看似簡(jiǎn)單而不易解決的問(wèn)題?。ㄒ韵掠胹ocket表示一個(gè)客戶(hù)端連接)
1 內(nèi)存布局圖
假設(shè)socket3有新的數(shù)據(jù)到達(dá),需要更新socket3所在的時(shí)間軸,處理邏輯如下:
2 處理過(guò)程分析:
基本的處理思路就是增加時(shí)間軸概念。將socket按最后更新時(shí)間排序。因?yàn)闀r(shí)間是連續(xù)的,不可能將時(shí)間分割太細(xì)。首先將時(shí)間離散,比如屬于同一秒內(nèi)的更新,被認(rèn)為是屬于同一個(gè)時(shí)間點(diǎn)。離散的時(shí)間間隔稱(chēng)為時(shí)間刻度,該刻度值可以根據(jù)具體情況調(diào)整??潭戎翟叫?,超時(shí)計(jì)算越精確;但是計(jì)算量增大。如果時(shí)間刻度為10毫秒,則一秒的時(shí)間長(zhǎng)度被劃分為100份。所以需要對(duì)更新時(shí)間做規(guī)整,代碼如下:
DateTime CreateNow() { DateTime now = DateTime.Now; int m = 0; if(now.Millisecond != 0) { if(_minimumScaleOfMillisecond == 1000) { now = now.AddSeconds(1); //尾數(shù)加1,確保超時(shí)值大于 給定的值 } else { //如果now.Millisecond為16毫秒,精確度為10毫秒。則轉(zhuǎn)換后為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); }
屬于同一個(gè)時(shí)間刻度的socket,被放入在一個(gè)哈希表中(見(jiàn)圖中Group)。存放socket的類(lèi)如下:
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); } }
定義一個(gè)List表示時(shí)間軸:
List<SameTimeKeyGroup<T>> _listTimeScale = new List<SameTimeKeyGroup<T>>();
在_listTimeScale 前端的時(shí)間較舊,所以鏈表前端就是有可能超時(shí)的socket。
當(dāng)有socket需要更新時(shí),需要快速知道socket所在的group。這樣才能將socket從舊的group移走,再添加到新的group中。需要新增一個(gè)鏈表:
Dictionary<T, SameTimeKeyGroup<T>> _socketToSameTimeKeyGroup = new Dictionary<T, SameTimeKeyGroup<T>>();
2.1 當(dāng)socket有新的數(shù)據(jù)到達(dá)時(shí),處理步驟:
- 查找socket的上一個(gè)群組。如果該群組對(duì)應(yīng)的時(shí)刻和當(dāng)前時(shí)刻相同(時(shí)間都已經(jīng)離散,才有可能相同),無(wú)需更新時(shí)間軸。
- 從舊的群組刪除,增加到新的群組。
public void UpdateTime(T key) { DateTime now = CreateNow(); //是否已存在,從上一個(gè)時(shí)間群組刪除 if (_socketToSameTimeKeyGroup.ContainsKey(key)) { SameTimeKeyGroup<T> group = _socketToSameTimeKeyGroup[key]; if (group.ContainKey(key)) { if (group.TimeStamp == now) //同一時(shí)間更新,無(wú)需移動(dòng) { return; } else { group.RemoveKey(key); _socketToSameTimeKeyGroup.Remove(key); } } } //從超時(shí)組 刪除 _timeoutSocketGroup.Remove(key); //加入到新組 SameTimeKeyGroup<T> groupFromScaleList = GetOrCreateSocketGroup(now, out bool newCreate); groupFromScaleList.AddKey(key); _socketToSameTimeKeyGroup.Add(key, groupFromScaleList); if (newCreate) { AdjustTimeout(); } }
2.2 獲取超時(shí)的socket
時(shí)間軸從舊到新,對(duì)比群組的時(shí)間與超時(shí)時(shí)刻。就是鏈表_listTimeScale,從0開(kāi)始查找。
/// <summary> ///timeLimit 值為超時(shí)時(shí)刻限制 ///比如DateTime.Now.AddMilliseconds(-1000);表示 返回一秒鐘以前的數(shù)據(jù) /// </summary> /// <param name="timeLimit">該時(shí)間以前的socket會(huì)被返回</param> /// <returns></returns> public List<T> GetTimeoutValue(DateTime timeLimit, bool remove = true) { if((DateTime.Now - timeLimit) > _maxSpan ) { Debug.Write("GetTimeoutSocket timeLimit 參數(shù)有誤!"); } //從超時(shí)組 讀取 List<T> result = new List<T>(); foreach(T key in _timeoutSocketGroup) { _timeoutSocketGroup.Add(key); } if(remove) { _timeoutSocketGroup.Clear(); } while (_listTimeScale.Count > 0) { //時(shí)間軸從舊到新,查找對(duì)比 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 使用舉例
//創(chuàng)建變量。最大超時(shí)時(shí)間為600秒,時(shí)間刻度為1秒 TimeSpanManage<Socket> _deviceActiveManage = TimeSpanManage<Socket>.Create(TimeSpan.FromSeconds(600), 1000); //當(dāng)有數(shù)據(jù)到達(dá)時(shí),調(diào)用更新函數(shù) _deviceActiveManage.UpdateTime(socket); //需要在線(xiàn)程或定時(shí)器中,每隔一段時(shí)間調(diào)用,找出超時(shí)的socket //找出超時(shí)時(shí)間超過(guò)600秒的socket。 foreach (Socket socket in _deviceActiveManage.GetTimeoutValue(DateTime.Now.AddSeconds(-600))) { socket.Close(); }
4 完整代碼
/// <summary> /// 超時(shí)時(shí)間 時(shí)間間隔處理 /// </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">最大時(shí)間時(shí)間</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(); //是否已存在,從上一個(gè)時(shí)間群組刪除 if (_socketToSameTimeKeyGroup.ContainsKey(key)) { SameTimeKeyGroup<T> group = _socketToSameTimeKeyGroup[key]; if (group.ContainKey(key)) { if (group.TimeStamp == now) //同一時(shí)間更新,無(wú)需移動(dòng) { return; } else { group.RemoveKey(key); _socketToSameTimeKeyGroup.Remove(key); } } } //從超時(shí)組 刪除 _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); } //從超時(shí)組 刪除 bool result2 = _timeoutSocketGroup.Remove(key); return result || result2; } /// <summary> ///timeLimit 值為超時(shí)時(shí)刻限制 ///比如DateTime.Now.AddMilliseconds(-1000);表示 返回一秒鐘以前的數(shù)據(jù) /// </summary> /// <param name="timeLimit">該時(shí)間以前的socket會(huì)被返回</param> /// <returns></returns> public List<T> GetTimeoutValue(DateTime timeLimit, bool remove = true) { if((DateTime.Now - timeLimit) > _maxSpan ) { Debug.Write("GetTimeoutSocket timeLimit 參數(shù)有誤!"); } //從超時(shí)組 讀取 List<T> result = new List<T>(); foreach(T key in _timeoutSocketGroup) { _timeoutSocketGroup.Add(key); } if(remove) { _timeoutSocketGroup.Clear(); } while (_listTimeScale.Count > 0) { //時(shí)間軸從舊到新,查找對(duì)比 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); //尾數(shù)加1,確保超時(shí)值大于 給定的值 } else { //如果now.Millisecond為16毫秒,精確度為10毫秒。則轉(zhuǎn)換后為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心跳超時(shí)檢測(cè)的思路(適用于超大量TCP連接情況下)的詳細(xì)內(nèi)容,更多關(guān)于c# socket心跳超時(shí)檢測(cè)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- c#基于WinForm的Socket實(shí)現(xiàn)簡(jiǎn)單的聊天室 IM
- C# Socket通信的實(shí)現(xiàn)(同時(shí)監(jiān)聽(tīng)多客戶(hù)端)
- C# 通過(guò)Socket讀取大量數(shù)據(jù)的示例
- SuperSocket封裝成C#類(lèi)庫(kù)的步驟
- C# 實(shí)現(xiàn)WebSocket服務(wù)端教程
- 利用C#實(shí)現(xiàn)SSLSocket加密通訊的方法詳解
- C# Socket編程實(shí)現(xiàn)簡(jiǎn)單的局域網(wǎng)聊天器的示例代碼
- C# 三種方式實(shí)現(xiàn)Socket數(shù)據(jù)接收
相關(guān)文章
Unity中的PostProcessScene實(shí)用案例深入解析
這篇文章主要為大家介紹了Unity中的PostProcessScene實(shí)用案例深入解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05C#基于JsonConvert解析Json數(shù)據(jù)的方法實(shí)例
最近初接觸C#語(yǔ)言,發(fā)現(xiàn)JSON解析這塊和JAVA差異過(guò)大,下面這篇文章主要給大家介紹了關(guān)于C#基于JsonConvert解析Json數(shù)據(jù)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04C# 將字節(jié)流轉(zhuǎn)換為圖片的實(shí)例方法
C# 將字節(jié)流轉(zhuǎn)換為圖片的實(shí)例方法,需要的朋友可以參考一下2013-03-03Unity UGUI LayoutRebuilder自動(dòng)重建布局介紹及使用
這篇文章主要為大家介紹了Unity UGUI LayoutRebuilder自動(dòng)重建布局介紹及使用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07DevExpress之ChartControl的SeriesTemplate實(shí)例
這篇文章主要介紹了DevExpress之ChartControl的SeriesTemplate用法實(shí)例,實(shí)現(xiàn)了餅狀Series百分比顯示的效果,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2014-10-10