c# socket心跳超時檢測的思路(適用于超大量TCP連接情況下)
假設(shè)一種情景:
TCP服務(wù)器有1萬個客戶端連接,如果客戶端5秒鐘不發(fā)數(shù)據(jù),則要斷開。服務(wù)端如何檢測客戶端是否超時?這看起來是一個非常簡單的問題,其實不然!
最簡單的處理方法是:
啟動一個線程,每隔一段時間,檢查每個連接是否超時。每次處理需要1萬次檢查。計算量太大!檢查的時間間隔不能太小,否則大大增加計算量;如果間隔時間太大,超時誤差會增大。
本文提出一種新穎的處理方法,就是針對這個看似簡單而不易解決的問題?。ㄒ韵掠胹ocket表示一個客戶端連接)
1 內(nèi)存布局圖

假設(shè)socket3有新的數(shù)據(jù)到達(dá),需要更新socket3所在的時間軸,處理邏輯如下:

2 處理過程分析:
基本的處理思路就是增加時間軸概念。將socket按最后更新時間排序。因為時間是連續(xù)的,不可能將時間分割太細(xì)。首先將時間離散,比如屬于同一秒內(nèi)的更新,被認(rèn)為是屬于同一個時間點。離散的時間間隔稱為時間刻度,該刻度值可以根據(jù)具體情況調(diào)整??潭戎翟叫?,超時計算越精確;但是計算量增大。如果時間刻度為10毫秒,則一秒的時間長度被劃分為100份。所以需要對更新時間做規(guī)整,代碼如下:
DateTime CreateNow()
{
DateTime now = DateTime.Now;
int m = 0;
if(now.Millisecond != 0)
{
if(_minimumScaleOfMillisecond == 1000)
{
now = now.AddSeconds(1); //尾數(shù)加1,確保超時值大于 給定的值
}
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);
}
屬于同一個時間刻度的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。
當(dāng)有socket需要更新時,需要快速知道socket所在的group。這樣才能將socket從舊的group移走,再添加到新的group中。需要新增一個鏈表:
Dictionary<T, SameTimeKeyGroup<T>> _socketToSameTimeKeyGroup = new Dictionary<T, SameTimeKeyGroup<T>>();
2.1 當(dāng)socket有新的數(shù)據(jù)到達(dá)時,處理步驟:
- 查找socket的上一個群組。如果該群組對應(yīng)的時刻和當(dāng)前時刻相同(時間都已經(jīng)離散,才有可能相同),無需更新時間軸。
- 從舊的群組刪除,增加到新的群組。
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);表示 返回一秒鐘以前的數(shù)據(jù)
/// </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 參數(shù)有誤!");
}
//從超時組 讀取
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 使用舉例
//創(chuàng)建變量。最大超時時間為600秒,時間刻度為1秒
TimeSpanManage<Socket> _deviceActiveManage = TimeSpanManage<Socket>.Create(TimeSpan.FromSeconds(600), 1000);
//當(dāng)有數(shù)據(jù)到達(dá)時,調(diào)用更新函數(shù)
_deviceActiveManage.UpdateTime(socket);
//需要在線程或定時器中,每隔一段時間調(diào)用,找出超時的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);表示 返回一秒鐘以前的數(shù)據(jù)
/// </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 參數(shù)有誤!");
}
//從超時組 讀取
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); //尾數(shù)加1,確保超時值大于 給定的值
}
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心跳超時檢測的思路(適用于超大量TCP連接情況下)的詳細(xì)內(nèi)容,更多關(guān)于c# socket心跳超時檢測的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#?連接本地數(shù)據(jù)庫的實現(xiàn)示例
本文主要介紹了C#?連接本地數(shù)據(jù)庫的實現(xiàn)示例,文中根據(jù)實例編碼詳細(xì)介紹的十分詳盡,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03
C#中DataGridView導(dǎo)出Excel的兩種方法
這篇文章主要介紹了C#中DataGridView導(dǎo)出Excel的兩種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
C#中DataGridView動態(tài)添加行及添加列的方法
這篇文章主要介紹了C#中DataGridView動態(tài)添加行及添加列的方法,涉及C#中DataGridView針對行與列動態(tài)操作的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-09-09
C#結(jié)合OpenCVSharp4使用直方圖算法實現(xiàn)圖片相似度比較
這篇文章主要為大家詳細(xì)介紹了C#如何結(jié)合OpenCVSharp4使用直方圖算法實現(xiàn)圖片相似度比較,文中的示例代碼簡潔易懂,需要的小伙伴可以參考下2023-09-09

