UNIX網絡編程——socket的keep-alive(轉)


第一部分

【需求】 不影響服務器處理的前提下,檢測客戶端程序是否被強制終了。

【現狀】 服務器端和客戶端的Socket都設定了keepalive屬性。 服務器端設定了探測次數等參數,客戶端、服務器只是打開了keepalive機能 服務器端起了一個監視線程,利用select來檢測socket是否被關閉。

下面這是我的一點膚淺理解。

1.  關於keep alive

       無論windows還是linux,keepalive就三個參數:

 

sk->keepalive_probes:探測次數
sk->keepalive_time   探測的超時
sk->keepalive_intvl 探測間隔

 

   對 於一個已經建立的tcp連接。如果在keepalive_time時間內雙方沒有任何的數據包傳輸,則開啟keepalive功能的一端將發送 keepalive數據包,若沒有收到應答,則每隔keepalive_intvl時間再發送該數據包,發送keepalive_probes次。一直沒有 收到應答,則發送rst包關閉連接。若收到應答,則將計時器清零。例如★:

 

sk->keepalive_probes = 3;
sk->keepalive_time   = 30;
sk->keepalive_intvl = 1;

 

  意 思就是說對於tcp連接,如果一直在socket上有數據來往就不會觸發keepalive,但是如果30秒一直沒有數據往來,則keep alive開始工作:發送探測包,受到響應則認為網絡,是好的,結束探測;如果沒有相應就每隔1秒發探測包,一共發送3次,3次后仍沒有相應,則發送RST包關閉連接,也就是從網絡開始到你的socket能夠意識到網絡異常,最多花33秒。但是如果沒有設置keep alive,可能你在你的socket(阻塞性)的上面,接收: recv會一直阻塞不能返回,除非對端主動關閉連接,因為recv不知道socket斷了。發送:取決於數據量的大小,只要底層協議棧的buffer能放下你的發送數據,應用程序級別的send就會一直成功返回。 直到buffer滿,甚至buffer滿了還要阻塞一段時間試圖等待buffer空閑。所以你對send的返回值的檢查根本檢測不到失敗。開啟了keep alive功能,你直接通過發送接收的函數返回值就可以知道網絡是否異常。設置的方法(應用層):

 

int keepalive = 1; // 開啟keepalive屬性
int keepidle = 60; // 如該連接在60秒內沒有任何數據往來,則進行探測
int keepinterval = 5; // 探測時發包的時間間隔為5 秒
int keepcount = 3; // 探測嘗試的次數。如果第1次探測包就收到響應了,則后2次的不再發。
setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive , sizeof(keepalive ));
setsockopt(rs, SOL_TCP, TCP_KEEPIDLE, (void*)&keepidle , sizeof(keepidle ));
setsockopt(rs, SOL_TCP, TCP_KEEPINTVL, (void *)&keepinterval , sizeof(keepinterval ));
setsockopt(rs, SOL_TCP, TCP_KEEPCNT, (void *)&keepcount , sizeof(keepcount ));

 

2.select和keep alive的關系

       select 是為單個進程使用多個socket而設計的,跟檢測連接無關,如果只是檢測一個socket的話,沒有必要使用select。開了keepalive機能 的話,每次調用recv或send時檢查返回值,判斷是否出錯或為0。如果出錯,再檢查errno查資料,看哪個或哪幾個錯誤號表示鏈接斷了或不存在就可 以了。

       另外,誰想定期檢查連接狀況,誰就啟用keep alive另一端可以不起,只是被動地對探測包進行響應這種響應是tcp協議的基本要求,跟keep alive無關。並不需要客戶端和服務器端都開啟keep alive。

 

3.測試結果

       按照例★的值在一端的socket上開啟keep alive,然后阻塞在一個recv或者不停的send,這個時候拔了網線,測試從拔掉網線到recv/send返回失敗的時間。

       在linux kernel里頭的測試發現,對於阻塞型的socket,當recv的時候,如果沒有設置keep alive,即使網線拔掉或者ifdown,recv很長時間不會返回,最長達17分鍾,雖然這個時間比linux的默認超時時間()短了很多。但是如果設置了keep alive,基本都在keepalive_time +keepalive_probes*keepalive_intvl =33秒內返回錯誤。

       但是對於循環不停send的socket,當拔掉網線后,會持續一段時間send返回成功(0~10秒左右,取決 於發送數據的量),然后send阻塞,因為協議層的buffer滿了,在等待buffer空閑,大概90秒左右后才會返回錯誤。由此看來,send的時候,keep alive似乎沒有起到作用,這個原因至今也不清楚。后來通過給send之前設置timer來解決的。

 

第二部分

       我們知道TCP連接關閉時,需要連接的兩端中的某一方發起關閉動作,如果某一方突然斷電,另外一端是無法知道的。tcp的keep_alive就是用以檢測異常的一種機制。

 

       有三個參數:

 

  • 發送心跳消息的間隔
  • 未收到回復時,重試的時間間隔
  • 重試的次數
  如果是Linux操作系統,這三個值分別為
huangcheng@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
huangcheng@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
huangcheng@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_keepalive_probes
9


 也就意味着每隔7200s(兩個小時)發起一次keepalive的報文,如果沒有回應,75秒后進行重試,最多重試9次即認為連接關閉。

       這三個選項分別對應TCP_KEEPIDLE、TCP_KEEPINTL和TCP_KEEPCNT的選項值,通過setsockopt進行設置。

 

       但是,tcp自己的keepalive有這樣的一個bug:

       正常情況下,連接的另一端主動調用colse關閉連接,tcp會通知,我們知道了該連接已經關閉。但是如果tcp連接的另一端突然掉線,或者重啟斷電,這個時候我們並不知道網絡已經關閉。而此時,如果有發送數據失敗,tcp會自動進行重傳重傳包的優先級高於keepalive,那就意味着,我們的keepalive總是不能發送出去。 而此時,我們也並不知道該連接已經出錯而中斷。在較長時間的重傳失敗之后,我們才會知道。

 

       為了避免這種情況發生,我們要在tcp上層,自行控制。對於此消息,記錄發送時間和收到回應的時間。如果長時間沒有回應,就可能是網絡中斷。如果長時間沒有發送,就是說,長時間沒有進行通信,可以自行發一個包,用於keepalive,以保持該連接的存在。

 

http://blog.csdn.net/ctthuangcheng/article/details/8596818

 

 


免責聲明!

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



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