有些網絡應用在網線斷開后重新連上的情況下tcp socket連接保持ESTABLISH狀態不變,假如應用程式不使用tcp的keepalive,在網線斷開之后,以前建立的 socket 鏈接仍然會保持在ESTABLISH 狀態不會改變。實際上tcp協議對這部分是有所處理的,需要服務端程式,在配置socket屬性時,使用 keepalive option,一旦有此配置,這些長時間無數據的鏈接會根據tcp的keepalive內核屬性,在大於(tcp_keepalive_time(tcp_keepalive_probes * tcp_keepalive_intvl))所對應的時間(單位為秒)之后,斷開這些鏈接。
關於keep alive無論windows,還是linux,keepalive就三個參數:
sk->keepalive_probes: 探測次數
sk->keepalive_time: 探測的超時
sk->keepalive_intvl: 探測間隔
對於一個已經建立的tcp連接,如果在keepalive_time時間內雙方沒有任何的數據包傳輸,則開啟keepalive功能的一端將發送 eepalive數據包,若沒有收到應答,則每隔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次后仍沒有相應,就關閉連接,也就是從網絡開始斷到你的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 ));
select和keep alive的關系
select是為單個線程使用多個socket而設計的,跟檢測連接無關,如果只是檢測一個socket的話,沒有必要使用select。開了keepalive機能的話,每次調用recv或send時檢查返回值,判斷是否出錯或為0。如果出錯,再檢查errno查資料,看哪個或哪幾個錯誤號表示鏈接斷了或不存在就可以了。
另外,誰想定期檢查連接狀況,誰就啟用keep alive。另一端可以不起,只是被動地對探測包進行響應,這種響應是tcp協議的基本要求,跟keep alive無關,並不需要客戶端和服務器端都開啟keep alive。
測試結果
按照以上舉例★的值在一端的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來解決的。