TCP心跳 | TCP keepAlive(轉)


應用層對於每個socket采用如下函數來開啟 keepalive機制,其參數將采用系統上述配置。

setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));

注意:keepalive是一個TCP協議包,並不是應用層數據包,意即通過recv等函數從應用層上是無法獲得該協議包。可通過抓包工具來看。

==================================================================

一、什么是keepalive定時器?[1]

在一個空閑的(idle)TCP連接上,沒有任何的數據流,許多TCP/IP的初學者都對此感到驚奇。也就是說,如果TCP連接兩端沒有任何一個進程在向對方發送數據,那么在這兩個TCP模塊之間沒有任何的數據交換。你可能在其它的網絡協議中發現有輪詢(polling),但在TCP中它不存在。言外之意就是我們只要啟動一個客戶端進程,同服務器建立了TCP連接,不管你離開幾小時,幾天,幾星期或是幾個月,連接依舊存在。中間的路由器可能崩潰或者重啟,電話線可能go down或者back up,只要連接兩端的主機沒有重啟,連接依舊保持建立。

這就可以認為不管是客戶端的還是服務器端的應用程序都沒有應用程序級(application-level)的定時器來探測連接的不活動狀態(inactivity),從而引起任何一個應用程序的終止。然而有的時候,服務器需要知道客戶端主機是否已崩潰並且關閉,或者崩潰但重啟。許多實現提供了存活定時器來完成這個任務。

存活定時器是一個包含爭議的特征。許多人認為,即使需要這個特征,這種對對方的輪詢也應該由應用程序來完成,而不是由TCP中實現。此外,如果兩個終端系統之間的某個中間網絡上有連接的暫時中斷,那么存活選項(option)就能夠引起兩個進程間一個良好連接的終止。例如,如果正好在某個中間路由器崩潰、重啟的時候發送存活探測,TCP就將會認為客戶端主機已經崩潰,但事實並非如此。

存活(keepalive)並不是TCP規范的一部分。在Host Requirements RFC羅列有不使用它的三個理由:(1)在短暫的故障期間,它們可能引起一個良好連接(good connection)被釋放(dropped),(2)它們消費了不必要的寬帶,(3)在以數據包計費的互聯網上它們(額外)花費金錢。然而,在許多的實現中提供了存活定時器。

一些服務器應用程序可能代表客戶端占用資源,它們需要知道客戶端主機是否崩潰。存活定時器可以為這些應用程序提供探測服務。Telnet服務器和Rlogin服務器的許多版本都默認提供存活選項。

個人計算機用戶使用TCP/IP協議通過Telnet登錄一台主機,這是能夠說明需要使用存活定時器的一個常用例子。如果某個用戶在使用結束時只是關掉了電源,而沒有注銷(log off),那么他就留下了一個半打開(half-open)的連接。在圖18.16,我們看到如何在一個半打開連接上通過發送數據,得到一個復位(reset)返回,但那是在客戶端,是由客戶端發送的數據。如果客戶端消失,留給了服務器端半打開的連接,並且服務器又在等待客戶端的數據,那么等待將永遠持續下去。存活特征的目的就是在服務器端檢測這種半打開連接。
二、keepalive如何工作?[1]

在此描述中,我們稱使用存活選項的那一段為服務器,另一端為客戶端。也可以在客戶端設置該選項,且沒有不允許這樣做的理由,但通常設置在服務器。如果連接兩端都需要探測對方是否消失,那么就可以在兩端同時設置(比如NFS)。

若在一個給定連接上,兩小時之內無任何活動,服務器便向客戶端發送一個探測段。(我們將在下面的例子中看到探測段的樣子。)客戶端主機必須是下列四種狀態之一:

1) 客戶端主機依舊活躍(up)運行,並且從服務器可到達。從客戶端TCP的正常響應,服務器知道對方仍然活躍。服務器的TCP為接下來的兩小時復位存活定時器,如果在這兩個小時到期之前,連接上發生應用程序的通信,則定時器重新為往下的兩小時復位,並且接着交換數據。

2) 客戶端已經崩潰,或者已經關閉(down),或者正在重啟過程中。在這兩種情況下,它的TCP都不會響應。服務器沒有收到對其發出探測的響應,並且在75秒之后超時。服務器將總共發送10個這樣的探測,每個探測75秒。如果沒有收到一個響應,它就認為客戶端主機已經關閉並終止連接。

3) 客戶端曾經崩潰,但已經重啟。這種情況下,服務器將會收到對其存活探測的響應,但該響應是一個復位,從而引起服務器對連接的終止。

4) 客戶端主機活躍運行,但從服務器不可到達。這與狀態2類似,因為TCP無法區別它們兩個。它所能表明的僅是未收到對其探測的回復。

服務器不必擔心客戶端主機被關閉然后重啟的情況(這里指的是操作員執行的正常關閉,而不是主機的崩潰)。當系統被操作員關閉時,所有的應用程序進程(也就是客戶端進程)都將被終止,客戶端TCP會在連接上發送一個FIN。收到這個FIN后,服務器TCP向服務器進程報告一個文件結束,以允許服務器檢測這種狀態。

在第一種狀態下,服務器應用程序不知道存活探測是否發生。凡事都是由TCP層處理的,存活探測對應用程序透明,直到后面2,3,4三種狀態發生。在這三種狀態下,通過服務器的TCP,返回給服務器應用程序錯誤信息。(通常服務器向網絡發出一個讀請求,等待客戶端的數據。如果存活特征返回一個錯誤信息,則將該信息作為讀操作的返回值返回給服務器。)在狀態2,錯誤信息類似於“連接超時”。狀態3則為“連接被對方復位”。第四種狀態看起來像連接超時,或者根據是否收到與該連接相關的ICMP錯誤信息,而可能返回其它的錯誤信息。

windows 實現:

 

在一個正常的TCP連接上,當我們用無限等待的方式調用下面的Recv或Send的時候:

   ret=recv(s,&buf[idx],nLeft,flags);

   或

   ret=send(s,&buf[idx],nLeft,flags);

   如果TCP連接被對方正常關閉,也就是說,對方是正確地調用了closesocket(s)或者shutdown(s)的話,那么上面的Recv或Send調用就能馬上返回,並且報錯。這是由於closesocket(s)或者shutdown(s)有個正常的關閉過程,會告訴對方“TCP連接已經關閉,你不需要再發送或者接受消息了”。但是,如果是網線突然被拔掉,TCP連接的任何一端的機器突然斷電或重啟動,那么這時候正在執行Recv或Send操作的一方就會因為沒有任何連接中斷的通知而一直等待下去,也就是會被長時間卡住。這種情形解決的辦法是啟動TCP編程里的keepAlive機制。

   struct TCP_KEEPALIVE inKeepAlive = {0};
     unsigned long ulInLen = sizeof(struct TCP_KEEPALIVE);
     struct TCP_KEEPALIVE utKeepAlive = {0};
     unsigned long ulOutLen = sizeof(struct TCP_KEEPALIVE);
     unsigned long ulBytesReturn = 0;

    inKeepAlive.onoff=1;
     inKeepAlive.keepaliveinterval=5000; //單位為毫秒
    inKeepAlive.keepalivetime=1000;      //單位為毫秒
    ret=WSAIoctl(s, SIO_KEEPALIVE_VALS, (LPVOID)&inKeepAlive, ulInLen, 
                           (LPVOID)&outKeepAlive, ulOutLen, &ulBytesReturn, NULL, NULL);

  此處的keepalivetime表示的是TCP連接處於暢通時候的探測頻率,一旦探測包沒有返回,就以keepaliveinterval的頻率發送,經過若干次的重試,如果探測包都沒有返回,那么就得出結論:TCP連接已經斷開,於是上面的Recv或Send調用也就能馬上返回,不會無限制地卡住了。

 

上圖是對上面文字的說明。亮條之前,TCP處於暢通狀態,KeepAlive是以1000毫秒(keepalivetime的值)的頻率發送探測包,在發送到第32個探測包的時候,探測包沒有返回,於是就以5000毫秒(keepalivetime的值)的頻率發送探測包,重發幾次后,探測包都沒有返回,於是就得出結論:此TCP連接已經斷開了!

 

對於Win2K/XP/2003,可以從下面的注冊表項找到影響整個系統所有連接的keepalive參數:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]

“KeepAliveTime”=dword:006ddd00 “KeepAliveInterval”=dword:000003e8 “MaxDataRetries”=”5″

 

  對於實用程序來說,2小時的空閑時間太長。因此,我們需要手工開啟Keepalive功能並設置合理的Keepalive參數。在XP和WIN2003系統上,可以針對單獨的socket來設置,但是在windows 2000,不能單獨設置,如果設置,那么影響是整個系統的所有socket。

linux實現:

SO_KEEPALIVE/TCP_KEEPCNT/TCP_KEEPIDLE/TCP_KEEPINTVL 如果一方已經關閉或異常終止連接,而另一方卻不知道,我們將這樣的TCP連接稱為半打開的。TCP通過保活定時器(KeepAlive)來檢測半打開連接。 在高並發的網絡服務器中,經常會出現漏掉socket的情況,對應的結果有一種情況就是出現大量的CLOSE_WAIT狀態的連接。這個時候,可以通過設置KEEPALIVE選項來解決這個問題,當然還有其他的方法可以解決這個問題,詳細的情況可以查看參考資料8。

使用方法如下:
//Setting For KeepAlive
 int keepalive = 1;
 setsockopt(incomingsock,SOL_SOCKET,SO_KEEPALIVE,(void*)(&keepalive),(socklen_t)sizeof(keepalive));
                         
 int keepalive_time = 30;
 setsockopt(incomingsock, IPPROTO_TCP, TCP_KEEPIDLE,(void*)(&keepalive_time),(socklen_t)sizeof(keepalive_time));
 int keepalive_intvl = 3;
 setsockopt(incomingsock, IPPROTO_TCP, TCP_KEEPINTVL,(void*)(&keepalive_intvl),(socklen_t)sizeof(keepalive_intvl));
int keepalive_probes= 3;
 setsockopt(incomingsock, IPPROTO_TCP, TCP_KEEPCNT,(void*)(&keepalive_probes),(socklen_t)sizeof(keepalive_probes));
設置SO_KEEPALIVE選項來開啟KEEPALIVE,然后通過TCP_KEEPIDLE、TCP_KEEPINTVL和TCP_KEEPCNT設置keepalive的開始時間、間隔、次數等參數。
 當然,也可以通過設置/proc/sys/net/ipv4/tcp_keepalive_time、tcp_keepalive_intvl和tcp_keepalive_probes等內核參數來達到目的,但是這樣的話,會影響所有的socket,因此建議使用setsockopt設置。

http://blog.csdn.net/cccallen/article/details/8003324


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM