在寫TCP服務的時候經常需要面對的問題就是如何知道一個TCP連接當前是否有效,但這個問題對很多初入門的同學來說是很困惑的,主要原因是當對方關閉連接后,另一方無法有效的知道;對於同步操作來說可以通過設置操作超時來解決,但異步操作則沒有這樣方便的了,那只能等keepalive的檢測完成引發異步回調了。
那在編寫應用的時候一般通訊什么方式來檢測連接的有效性呢?解決方法一般有兩種一種是設置TCP的keepalive時間,另一種則是通過Ping,Pong的方式來實現。前者相對比較簡單通過socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null)方法設置即可,以下主要但要通過Ping,Pong的方式來實現應用層面的TCP連接有效性檢測。通過Ping,Pong來處理有兩種方式:服務器主動和被動。
主動
這種方式主要是由服務器發起,然后由客戶端響應;服務檢測每個連接Pong響應情況,如果連接在一段時間內沒有Pong回應則把相應連接關閉並處理相關會話資源。
被動
這種方式由Client發起Ping然后由服務端回應Pong,如果Client是同步操作的話其實服務端是不需要應答Pong包。服務端檢測每個連接最近的Ping時間,如果超過一段時間沒有Ping的情況把相應連接關閉並處理相關會話資源。
模式選擇
從上面的兩種方式來看顯然是被動模式更節省服務器資源,如果采用主動的話服務器還必須啟用一個定時器對現有在線接進行發送Ping操作;被動模式就完全不需要了,只有接收到客戶端Ping回應一個Pong操作。
檢測算法
一般情況會用一個定時器隔一段時間對所有Client檢測一次,看對應的Ping時間是否超時,如果是則直接關閉和釋放資源。但這樣是要對所有連接進行掃描,其實在應用中只有很小部分連接是無效的,如果針對所有在線連接進行一個掃描那的確一個比較花成本的工作。為了解決全掃描的情況,可以采用一種簡單的算法LRU,通過LRU算法在檢測的時候只要掃冷區數據即可,這樣就可以達到只掃描Ping超時的連接。LRU具體處理結構如下:
以下給出相關LRU實現的c#版本代碼:
/// <summary> /// 基於LRU算法的連接檢測 /// </summary> public class LRUDetect:IDisposable { /// <summary> /// 構建檢測器 /// </summary> /// <param name="timeout">超時時間以毫秒為單位</param> public LRUDetect(int timeout) { mTimeout = timeout; mTimer = new System.Threading.Timer(OnDetect, null, mTimeout, mTimeout); } private int mTimeout; private System.Threading.Timer mTimer; private LinkedList<Node> mLinkedList = new LinkedList<Node>(); /// <summary> /// 更新連接 /// </summary> /// <param name="connection">連接信息</param> public void Update(IConnecton connection) { lock (this) { LinkedListNode<LRUDetect.Node> node = connection.Node; if (node != null) { node.Value.LastActiveTime = Environment.TickCount; mLinkedList.Remove(node); mLinkedList.AddFirst(node); } else { node = mLinkedList.AddFirst(new Node()); node.Value.LastActiveTime = Environment.TickCount; node.Value.Connection = connection; connection.Node = node; } } } /// <summary> /// 刪除連接 /// </summary> /// <param name="connection">連接信息</param> public void Delete(IConnecton connection) { lock (this) { LinkedListNode<LRUDetect.Node> node = connection.Node; if (node != null) { node.Value.Connection = null; mLinkedList.Remove(node); } } } private void OnDetect(object state) { lock (this) { int cutime = Environment.TickCount; LinkedListNode<Node> last = mLinkedList.Last; while (last !=null && last.Value.Detect(cutime,mTimeout)) { last.Value.Connection.TimeOut(); last.Value.Connection = null; mLinkedList.RemoveLast(); last = mLinkedList.Last; } } } /// <summary> /// 連接描述接口 /// </summary> public interface IConnecton { /// <summary> /// 獲取對應在LRU算法中的節點 /// </summary> LinkedListNode<LRUDetect.Node> Node { get; set; } /// <summary> /// 超時操作,當LRU算法檢測到應該連接超時的時候會調用該方法 /// </summary> void TimeOut(); } /// <summary> /// 節點信息 /// </summary> public class Node { /// <summary> /// 最后活動時間 /// </summary> public int LastActiveTime; /// <summary> /// 相關連接信息 /// </summary> public IConnecton Connection; /// <summary> /// 檢測是否過期 /// </summary> /// <param name="cutime"></param> /// <param name="timeout"></param> /// <returns></returns> public bool Detect(int cutime,int timeout) { return Math.Abs(cutime - LastActiveTime) > timeout; } } /// <summary> /// 釋放對象 /// </summary> public void Dispose() { if (mTimer != null) mTimer.Dispose(); } }