(轉)TCP連接異常斷開檢測


TCP是一種面向連接的協議,連接的建立和斷開需要通過收發相應的分節來實現。某些時候,由於網絡的故障或是一方主機的突然崩潰而另一方無法檢測到,以致始終保持着不存在的連接。下面介紹一種方法來檢測這種異常斷開的情況

 

TCP是一種面向連接的協議,連接的建立和斷開需要通過收發相應的分節來實現。某些時候,由於網絡的故障或是一方主機的突然崩潰而另一方無法檢測到,以致始終保持着不存在的連接。下面介紹一種方法來檢測這種異常斷開的情況

1) 在TCP協議中提供了KEEPALIVE檢測。該選項使能后,在一個TCP連接上,若指定的一段時間內沒有數據交換,則自動發送分節等待對方確認。

     SO_KEEPALIVE : 該選項設置是否打開探測
             TCP_KEEPIDLE : 開始發送探測分節前等待的空閑時間
             TCP_KEEPINTVL: 兩次發送探測分節的時間間隔
             TCP_KEEPCNT: 判定斷開前發送探測分節的次數

2) 設定探測相關選項值

     int keepalive = 1;             // 打開探測
             int keepidle = 60;        // 開始探測前的空閑等待時間
             int keepintvl = 10;        // 發送探測分節的時間間隔
             int keepcnt = 3;        // 發送探測分節的次數

3) 設置套接字的屬性

     if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive, sizeof (keepalive) < 0)
             {
                     perror(“fail to set SO_KEEPALIVE”);
                     exit(-1);
             }
             if (setsockopt(sockfd, SOL_TCP, TCP_KEEPIDLE, (void *) &keepidle, sizeof (keepidle) < 0)
             {
                     perror(“fail to set SO_KEEPIDLE”);
                     exit(-1);
             }
             if (setsockopt(sockfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepintvl, sizeof (keepintvl) < 0)
             {
                     perror(“fail to set SO_KEEPINTVL”);
                     exit(-1);
             }
             if (setsockopt(sockfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepcnt, sizeof (keepcnt) < 0)
             {
                     perror(“fail to set SO_KEEPALIVE”);
                     exit(-1);
             }

一旦打開KEEPALIVE探測,當TCP連接異常斷開后,對sockfd進行recv操作會返回-1,並且errno的值為ETIMEDOUT。

這樣一來就可以很方便的在應用程序中檢測TCP連接的情況,如果檢測到異常斷開最簡單的處理就是關閉連接。

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

WinSock TCP keepalive的機理及使用

TCP 是面向連接的 , 在實際應用中通常都需要檢測對端是否還處於連接中。如果已斷開連接,主要分為以下幾種情況:
1.  連接的對端正常關閉,即使用 closesocket 關閉連接。
2.  連接的對端非正常關閉,包括對端異常關閉,網絡斷開等情況。

對於第一種情況,很好判斷,但是對於第二種情況,可能會要麻煩一些。在網上找到了一些文章,大致有以下兩種解決方法:
Ÿ           自己編寫心跳包程序
簡單的說也就是在自己的程序中加入一條線程,定時向對端發送數據包,查看是否有 ACK ,如果有則連接正常,沒有的話則連接斷開。
Ÿ           使用 TCP 的 keepalive 機制
這個需要在 WinSock 編程時對當前 SOCKET 進行相應設置即可,比較方便。

為了方便起見,我這里采用 keepalive 機制,下面我就以 WinSock 上我實驗得到的結果來大致講一下其機理和使用方法。

首先說一下 keepalive 來判斷異常斷開的原理,其實 keepalive 的原理就是 TCP 內嵌的一個心跳包。

服務器端為例,如果當前 server 端檢測到超過一定時間(默認是 7,200,000 milliseconds ,也就是 2 個小時)沒有數據傳輸,那么會 向client 端發送一個 keep-alive packet (該 keep-alive packet 就是 ACK 和當前 TCP 序列號減一的組合),此時 client 端應該為以下三種情況之一:

1. client 端仍然存在,網絡連接狀況良好。此時 client 端會返回一個 ACK 。 server 端接收到 ACK 后重置計時器,在 2 小時后再發送探測。如果 2 小時內連接上有數據傳輸,那么在該時間基礎上向后推延 2 個小時。
2. 客戶端異常關閉,或是網絡斷開。在這兩種情況下, client 端都不會響應。服務器沒有收到對其發出探測的響應,並且在一定時間(系統默認為 1000 ms )后重復發送 keep-alive packet ,並且重復發送一定次數( 2000 XP 2003 系統默認為 5 次 , Vista 后的系統默認為 10 次)。
3. 客戶端曾經崩潰,但已經重啟。這種情況下,服務器將會收到對其存活探測的響應,但該響應是一個復位,從而引起服務器對連接的終止。(這條摘抄自http://www.cppblog.com/zhangyq/archive/2010/02/28/108615.html ,我自己並不太明白)。

了解了 keep alive 大致的原理,下來看看在程序中怎么用,怎么設置參數:

  1. #include <mstcpip.h> 
  2. BOOL bKeepAlive = TRUE; 
  3. int nRet = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,(char*)&bKeepAlive, sizeof(bKeepAlive)); 
  4.  if (nRet == SOCKET_ERROR) 
  5.  { 
  6.   TRACE(L"setsockopt failed: %d\n", WSAGetLastError()); 
  7.   return FALSE; 
  8.  } 
  9.  // set KeepAlive parameter 
  10.  tcp_keepalive alive_in; 
  11.  tcp_keepalive alive_out; 
  12.  alive_in.keepalivetime    = 500;  // 0.5s 
  13.  alive_in.keepaliveinterval  = 1000; //1s 
  14.  alive_in.onoff                       = TRUE; 
  15.  unsigned long ulBytesReturn = 0; 
  16.  nRet = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &alive_in, sizeof(alive_in), 
  17.                 &alive_out, sizeof(alive_out), &ulBytesReturn, NULL, NULL); 
  18.  if (nRet == SOCKET_ERROR) 
  19.  { 
  20.   TRACE(L"WSAIoctl failed: %d\n", WSAGetLastError()); 
  21.   return FALSE; 
  22.  } 

其中, setsockopt 設置了 keepalive 模式,但是系統對 keepalive 默認的參數可能不符合我們的要求,比如空閑 2 小時后才探測對端是否活躍,所以 WSAIoctl 函數通過 tcp_keepalive 結構體對這些參數進行了相應設置。 tcp_keepalive 這 個 結構體在 mstcpip.h 頭文件中有定義:

  1. struct tcp_keepalive { 
  2.   ULONG onoff ;   // 是否開啟 keepalive 
  3.   ULONG keepalivetime ;  // 多長時間( ms )沒有數據就開始 send 心跳包 
  4.   ULONG keepaliveinterval ; // 每隔多長時間( ms ) send 一個心跳包, 
  5.   // 發 5 次 (2000 XP 2003 默認 ), 10 次 (Vista 后系統默認 ) 
  6. }; 

這個結構體設置了空閑檢測時間,及檢測時重復發送的間隔時間。詳細的可以查詢 msdn :http://msdn.microsoft.com/en-us/library/dd877220(VS.85).aspx 。

按照 msdn 上的說法,這些參數也可以通過在注冊表里設置,分別為:
HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\KeepAliveTime
HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\KeepAliveInterval

另外,有些人可能已經發現了, tcp_keepalive 這 個結構體中沒有對重試次數這個參數的設置,這個參數可以通過注冊表來設置,具體位置為:

HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpMaxDataRetransmissions

關於在注冊表中設置這幾個參數,我在 XP 和 Server2008 系統中都沒有找到, msdn 上說貌似只是支持 server 2003 ,我這里沒有實驗,具體不太清楚。

設置好 keepalive 以后,我們通過實驗來看看當 client 異常退出或是網絡斷掉的情況下, keepalive 怎么通知我們異常斷開的情況。這里采用 select 模式,實驗環境為 XP 系統和 Win7 系統,幾種情況返回值如下:

 

1. 正常斷開
select 函數正常返回, recv 函數返回 0
2. 異常斷開
a)       程序異常退出,如 client 端重啟,應用非正常關閉等
select 函數正常返回, recv 函數返回 SOCKET_ERROR , WSAGetLastError () 得到的結果為 WSAECONNRESET(10054) 。
b)      網絡斷開
結果同上: select 函數正常返回, recv 函數返回 SOCKET_ERROR , WSAGetLastError() 得到的結果為 WSAECONNRESET(10054) 。

P.S. 網上有些文章中寫的 WSAGetLastError() 得到的結果為 ETIMEDOUT ,我這里不太清楚為什么和我這里得到的不太一樣。

另外,在實驗中,我發現了一個和以前理解的不太相同的地方,在這里也記錄下來:

對於程序異常退出的情況(這里所說的異常退出包括程序異常關閉、重啟等情況,但不包括系統待機休眠),實際上在不開啟 keepalive 的情況下也是可以檢測到的 ,我這里測試得到在不開啟 keepalive 的情況下,異常關閉 client 端程序, server 端 recv 函數會立即返回 SOCKET_ERROR , last error 同樣 為 WSAECONNRESET 。但是對於網絡斷開及系統待機休眠的情況,則必須設置 keepalive 才能檢測到,並且對於上述情況,當網絡重新連接或者系統恢復后,SOCKET連接並不能恢復。

具體原因我這里也不太清楚,看到有一篇文章是這樣寫的:“異常關閉下, SOCKET 虛擬通路會被重設,遠端正在接受的調用就都會失敗”。不知道正確與否,感覺有一定的道理,暫時記錄下來。

 

轉自:http://www.rosoo.net/a/201505/17296.html?utm_source=tuicool


免責聲明!

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



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