【需求】
不影響伺服器處理的前提下,檢測用戶端程式是否被強制終了。
【現狀】
伺服器端和用戶端的Socket都設定了keepalive屬性。
伺服器端設定了探測次數等參數,用戶端、伺服器只是開啟了keepalive機能
伺服器端起了一個監視線程,利用select來檢測socket是否被關閉。。。
下面這是我的一點膚淺理解。
1.關於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 ));
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來解決的。