1. 程式人生 > >TCP執行緒出現非正常斷開的解決方法

TCP執行緒出現非正常斷開的解決方法

在多執行緒任務中,TCP任務通過三次握手能建立可靠的連線,但是經常會發生在資料傳輸或通訊時發生網路突然斷開或者長時間連線空迴圈監聽而未進行操作,需要在軟體設計時考慮程式執行中檢測到伺服器對客戶端的這一“虛連線”現象。
    如果主機崩潰,write是否阻塞取決於核心的tcp緩衝區,但read將一直阻塞,直到超時ETIMEOUT,或由於某些中間路由器的原因返回EHOSTUNREACH/ENETUNREACH。select不能檢測到該情況。
如果主機崩潰並重起,客戶的write到達主機時主機響應RST,客戶的read將返ECONNRESET。
此處的”非正常斷開”指TCP連線不是以優雅的方式斷開,如網線故障等物理鏈路的原因,還有突然主機斷電等原因 。
   心跳機制
    有兩種方法可以檢測:1.TCP連線雙方定時發握手訊息 2.利用TCP協議棧中的KeepAlive探測
第二種方法簡單可靠,只需對TCP連線兩個Socket設定KeepAlive探測,所以本文只講第二種方法在Linux,Window2000下的實現(在其它的平臺上沒有作進一步的測試)
1)Windows平臺
C程式碼
//定義結構及巨集 
struct TCP_KEEPALIVE {   
    u_longonoff;   
    u_longkeepalivetime;   
    u_longkeepaliveinterval;   
} ;   
#define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4)
//KeepAlive實現 
TCP_KEEPALIVE inKeepAlive = {0}; //輸入引數 
unsigned long ulInLen = sizeof(TCP_KEEPALIVE);   
TCP_KEEPALIVE outKeepAlive = {0}; //輸出引數 
unsigned long ulOutLen = sizeof(TCP_KEEPALIVE);   
unsigned long ulBytesReturn = 0;   
//設定socket的keep alive為5秒,並且傳送次數為3次 
inKeepAlive.onoff = 1;   
inKeepAlive.keepaliveinterval = 5000; 
//兩次KeepAlive探測間的時間間隔
inKeepAlive.keepalivetime = 5000; //開始首次KeepAlive探測前的TCP空閉時間 
if (WSAIoctl((unsigned int)s, SIO_KEEPALIVE_VALS,   
(LPVOID)&inKeepAlive, ulInLen,   
(LPVOID)&outKeepAlive, ulOutLen,   
&ulBytesReturn, NULL, NULL) == SOCKET_ERROR)   
{   
    ACE_DEBUG ((LM_INFO,   
    ACE_TEXT ("(%P|%t) \WSAIoctl failed. error code(%d)!\n"), WSAGetLastError()));   
}  

心跳機制:定時傳送一個自定義的結構體(心跳包),讓對方知道自己還活著,以確保連線的有效性。

網路中的接收和傳送資料都是使用WINDOWS中的SOCKET進行實現。但是如果此套接字已經斷開,那傳送資料和接收資料的時候就一定會有問題。可是如何判斷這個套接字是否還可以使用呢?這個就需要在系統中建立心跳機制。其實TCP中已經為我們實現了一個叫做心跳的機制。如果你設定了心跳,那TCP就會在一定的時間(比如你設定的是3秒鐘)內傳送你設定的次數的心跳(比如說2次),並且此資訊不會影響你自己定義的協議。所謂“心跳”就是定時傳送一個自定義的結構體(心跳包或心跳幀),讓對方知道自己“線上”。以確保連結的有效性。

  所謂的心跳包就是客戶端定時傳送簡單的資訊給伺服器端告訴它我還在而已。程式碼就是每隔幾分鐘傳送一個固定資訊給服務端,服務端收到後回覆一個固定資訊如果服務端幾分鐘內沒有收到客戶端資訊則視客戶端斷開。比如有些通訊軟體長時間不使用,要想知道它的狀態是線上還是離線就需要心跳包,定時發包收包。發包方:可以是客戶也可以是服務端,看哪邊實現方便合理。一般是客戶端。伺服器也可以定時輪詢發心跳下去。心跳包之所以叫心跳包是因為:它像心跳一樣每隔固定時間發一次,以此來告訴伺服器,這個客戶端還活著。事實上這是為了保持長連線,至於這個包的內容,是沒有什麼特別規定的,不過一般都是很小的包,或者只包含包頭的一個空包。在TCP的機制裡面,本身是存在有心跳包的機制的,也就是TCP的選項。系統預設是設定的是2小時的心跳頻率。但是它檢查不到機器斷電、網線拔出、防火牆這些斷線。而且邏輯層處理斷線可能也不是那麼好處理。一般,如果只是用於保活還是可以的。心跳包一般來說都是在邏輯層傳送空的包來實現的。下一個定時器,在一定時間間隔下發送一個空包給客戶端,然後客戶端反饋一個同樣的空包回來,伺服器如果在一定時間內收不到客戶端傳送過來的反饋包,那就只有認定說掉線了。只需要send或者recv一下,如果結果為零,則為掉線。但是,在長連線下,有可能很長一段時間都沒有資料往來。理論上說,這個連線是一直保持連線的,但是實際情況中,如果中間節點出現什麼故障是難以知道的。更要命的是,有的節點(防火牆)會自動把一定時間之內沒有資料互動的連線給斷掉。在這個時候,就需要我們的心跳包了,用於維持長連線,保活。在獲知了斷線之後,伺服器邏輯可能需要做一些事情,比如斷線後的資料清理呀,重新連線呀當然,這個自然是要由邏輯層根據需求去做了。總的來說,心跳包主要也就是用於長連線的保活和斷線處理。一般的應用下,判定時間在30-40秒比較不錯。如果實在要求高,那就在6-9秒。

TCP連線異常斷開後作業系統會告訴你,你查詢套接字的狀態會得到異常,或者當發現函式失敗WSAGetLastError的時候也會得到核心的通知。

// 傳送迴應訊息 int nSend = Send4IntMsg(sock, (char*)(LPCTSTR)strSendBuf, strSendBuf.GetLength(), errMsg); if (nSend < 0) // 傳送訊息失敗 closesocket(sock);//重新連線

在B/S程式設計和UDP程式設計時才用到心跳。比如定期向web伺服器發一個request證明自己線上。http協議是請求一下就斷開了,每次都要重新連線,重新請求,這種情況下才有必要用心跳機制。一般的TCP通訊都是長連線,不可能頻繁連線和斷開。對於長期保持連線的情況,一旦斷開,作業系統底層都會通知你,你需要解決的是如何獲取到系統的通知。