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

第一部分linux

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

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

下面這是個人一點膚淺理解。服務器

1.  關於keep alive網絡

       不管windows仍是linux,keepalive就三個參數:socket

 

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

 

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

 

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查資料,看哪一個或哪幾個錯誤號表示連接斷了或不存在就可 以了。spa

       另外,誰想按期檢查鏈接情況,誰就啓用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

相關文章
相關標籤/搜索