1. 程式人生 > >socket之KEEPALIVE機制與原理分析

socket之KEEPALIVE機制與原理分析

LINUX之TCP連線時間----TCP keepAlive 詳解 (2011-08-11 11:07:04)轉載
標籤: 雜談 分類: linux
在一個正常的TCP連線上,當我們用無限等待的方式呼叫下面的Recv或Send的時候:

   ret=recv(s,&buf[idx],nLeft,flags);

   或

   ret=send(s,&buf[idx],nLeft,flags);

   如果TCP連線被對方正常關閉,也就是說,對方是正確地呼叫了closesocket(s)或者shutdown(s)的話,那麼上面的Recv或Send呼叫就能馬上返回,並且報錯。這是由於closesocket(s)或者shutdown(s)有個正常的關閉過程,會告訴對方“TCP連線已經關閉,你不需要再發送或者接受訊息了”。但是,如果是網線突然被拔掉,TCP連線的任何一端的機器突然斷電或重啟動,那麼這時候正在執行Recv或Send操作的一方就會因為沒有任何連線中斷的通知而一直等待下去,也就是會被長時間卡住。這種情形解決的辦法

是啟動TCP程式設計裡的keepAlive機制。

    struct TCP_KEEPALIVE inKeepAlive = {0};
    unsigned long ulInLen = sizeof(struct TCP_KEEPALIVE);
    struct TCP_KEEPALIVE outKeepAlive = {0};
    unsigned long ulOutLen = sizeof(struct TCP_KEEPALIVE);
    unsigned long ulBytesReturn = 0;

    inKeepAlive.onoff=1;
    inKeepAlive.keepaliveinterval=5000; //單位為毫秒
    inKeepAlive.keepalivetime=1000;      //單位為毫秒
    ret=WSAIoctl(s, SIO_KEEPALIVE_VALS, (LPVOID)&inKeepAlive, ulInLen,

                          (LPVOID)&outKeepAlive, ulOutLen, &ulBytesReturn, NULL, NULL);

   此處的keepalivetime表示的是TCP連線處於暢通時候的探測頻率,一旦探測包沒有返回,就以keepaliveinterval

的頻率傳送,經過若干次的重試,如果探測包都沒有返回,那麼就得出結論:TCP連線已經斷開,於是上面的Recv或Send呼叫也就能馬上返回,不會無限制地卡住了。

上圖是對上面文字的說明。亮條之前,TCP處於暢通狀態,KeepAlive是以1000毫秒(keepalivetime的值)的頻率傳送探測包,

在傳送到第32個探測包的時候,探測包沒有返回,於是就以5000毫秒(keepalivetime的值)的頻率傳送探測包,重發幾次後,

探測包都沒有返回,於是就得出結論:此TCP連線已經斷開了!

對於Win2K/XP/2003,可以從下面的登錄檔項找到影響整個系統所有連線的keepalive引數:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]

“KeepAliveTime”=dword:006ddd00
“KeepAliveInterval”=dword:000003e8
“MaxDataRetries”=”5″

  對於實用程式來說,2小時的空閒時間太長。因此,我們需要手工開啟Keepalive功能並設定合理的Keepalive引數。在XP和

WIN2003系統上,可以針對單獨的socket來設定,但是在windows 2000,不能單獨設定,如果設定,那麼影響是整個系統的所有socket。

LINUX之TCP連線時間----WinSock TCP keepalive的機理及使用 (2011-08-11 11:08:20)轉載
標籤: 雜談 分類: linux
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 大致的原理,下來看看在程式中怎麼用,怎麼設定引數:   
 
view plain
#include <mstcpip.h>  
       BOOL bKeepAlive = TRUE;  
int nRet = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,   
                             (char*)&bKeepAlive, sizeof(bKeepAlive));  
if (nRet == SOCKET_ERROR)  
{  
    TRACE(L"setsockopt failed: %d/n", WSAGetLastError());  
    return FALSE;  
}  
// set KeepAlive parameter  
tcp_keepalive alive_in;  
tcp_keepalive alive_out;  
alive_in.keepalivetime    = 500;  // 0.5s  
alive_in.keepaliveinterval  = 1000; //1s  
alive_in.onoff                       = TRUE;  
unsigned long ulBytesReturn = 0;  
nRet = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &alive_in, sizeof(alive_in),  
                  &alive_out, sizeof(alive_out), &ulBytesReturn, NULL, NULL);  
if (nRet == SOCKET_ERROR)  
{  
    TRACE(L"WSAIoctl failed: %d/n", WSAGetLastError());  
    return FALSE;  
}  
   
其中, setsockopt 設定了 keepalive 模式,但是系統對 keepalive 預設的引數可能不符合我們的要求,比如空閒 2 小時後才探測對端是否活躍,所以 WSAIoctl 函式通過 tcp_keepalive 結構體對這些引數進行了相應設定。 tcp_keepalive 這 個 結構體在 mstcpip.h 標頭檔案中有定義:
    struct tcp_keepalive {
        ULONG onoff ;   // 是否開啟 keepalive
        ULONG keepalivetime ;  // 多長時間( ms )沒有資料就開始 send 心跳包
  ULONG keepaliveinterval ; // 每隔多長時間( ms ) send 一個心跳包,
// 發 5 次 (2000 XP 2003 預設 ), 10 次 (Vista 後系統預設 )
};
這個結構體設定了空閒檢測時間,及檢測時重複傳送的間隔時間。詳細的可以查詢 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 虛擬通路會被重設,遠端正在接受的呼叫就都會失敗”。不知道正確與否,感覺有一定的道理,暫時記錄下來。

[轉載]服務端程式的keeplive (2011-08-11 11:06:46)轉載原文
標籤: 轉載 分類: linux
原文地址:服務端程式的keeplive作者:天涯孤客
轉載自:http://blog.csdn.net/vincent2600/archive/2010/12/01/6046858.aspx
首先就看一下KeepAlive出現的原因吧: 
當一個客戶端向伺服器傳送http請求時,兩者之間會建立一個tcp連線,然後伺服器發回響應資訊同時關閉連線。如果請求的的頁面中含有別的資源連線,比如圖片、flsah等,就會再次建立連線。KeepAlive的作用就是在第一次建立連線時,伺服器會把這個tcp連線保持一段時間(伺服器端會有一個keepaliveTime的最大時間,超過時間就斷開連線)。這樣就不會頻繁的去建立tcp連線,同一次請求中的資訊傳遞都可以使用同一個tcp連線。

KeepAlive的工作原理: 
在HTTP1.0和HTTP1.1協議中都有對KeepAlive的支援。其中HTTP1.0需要在request中增加“Connection: keep-alive” header才能夠支援,而HTTP1.1預設支援。(大家可以利用抓包工具看一下) HTTP1.0 KeepAlive支援的資料互動流程如下: 
a)Client發出request,其中該request的HTTP版本號為1.0。同是在request中包含一個header:

“Connection: keep-alive”。 
b)Web Server收到request中的HTTP協議為1.0及“Connection: keep-alive”就認為是一個長連線請求,其將在response的header中也增加“Connection: keep-alive”。同是不會關閉已建立的tcp連線。 
c)Client收到Web Server的response中包含“Connection: keep-alive”,就認為是一個長連線,不close tcp連線。並用該tcp連線再發送request。(跳轉到a)) 
HTTP1.1 KeepAlive支援的資料互動流程如下: 
a)Client發出request,其中該request的HTTP版本號為1.1。 
b)Web Server收到request中的HTTP協議為1.1就認為是一個長連線請求,其將在response的header中也增加“Connection: keep-alive”。同是不會關閉已建立的tcp連線。 
c)Client收到Web Server的response中包含“Connection: keep-alive”,就認為是一個長連線,不close tcp連線。並用該tcp連線再發送request。(跳轉到a))

關於KeepAlive的分析: 
現在的一些伺服器都可以設定KeepAlive是否開啟,以及KeepAlive的超時時間,伺服器支援的KeepAlive數量(數量一般不會很大,否則會對伺服器產生很大的壓力)。 
那麼我們考慮3種情況: 
  1、使用者瀏覽一個網頁時,除了網頁本身外,還引用了多個 javascript 檔案,多個 css 檔案,多個圖片檔案,並且這些檔案都在同一個 HTTP 伺服器上。 
  2、使用者瀏覽一個網頁時,除了網頁本身外,還引用一個 javascript 檔案,一個圖片檔案。 
  3、使用者瀏覽的是一個動態網頁,由程式即時生成內容,並且不引用其他內容。 
對於上面3中情況,1 最適合開啟 KeepAlive ,2 隨意,3 最適合關閉 KeepAlive 打 開 KeepAlive 後,意味著每次使用者完成全部訪問後,都要保持一定時間後才關閉會關閉 TCP 連線,那麼在關閉連線之前,必然會有一個伺服器程序對應於該使用者而不能處理其他使用者,假設 KeepAlive 的超時時間為 10 秒種,伺服器每秒處理 50 個獨立使用者訪問,那麼系統中 Apache 的總程序數就是

10 * 50 = 500 個,如果一個程序佔用 4M 記憶體,那麼總共會消耗 2G 記憶體,所以可以看出,在這種配置中,相當消耗記憶體,但好處是系統只處理了 50次 TCP 的握手和關閉操作。 
如果關閉 KeepAlive,如果還是每秒50個使用者訪問,如果使用者每次連續的請求數為3個,那麼 Apache 的總程序數就是 50 * 3 = 150 個,如果還是每個程序佔用 4M 記憶體,那麼總的記憶體消耗為 600M,這種配置能節省大量記憶體,但是,系統處理了 150 次 TCP 的握手和關閉的操作,因此又會多消耗一些 CPU 資源。

採用TCP連線的C/S模式軟體,連線的雙方在連線空閒狀態時,如果任意一方意外崩潰、當機、網線斷開或路由器故障,另一方無法得知TCP連線已經失效,除非繼續在此連線上傳送資料導致錯誤返回。很多時候,這不是我們需要的。我們希望伺服器端和客戶端都能及時有效地檢測到連線失效,然後優雅地完成一些清理工作並把錯誤報告給使用者。

如何及時有效地檢測到一方的非正常斷開,一直有兩種技術可以運用。一種是由TCP協議層實現的Keepalive,另一種是由應用層自己實現的心跳包。TCP預設並不開啟Keepalive功能,因為開啟Keepalive功能需要消耗額外的寬頻和流量,儘管這微不足

道,但在按流量計費的環境下增加了費用,另一方面,Keepalive設定不合理時可能會因為短暫的網路波動而斷開健康的TCP連線。並且,預設的Keepalive超時需要7,200,000 milliseconds,即2小時,探測次數為5次。對於Win2K/XP/2003,可以從下面的登錄檔項找到影響整個系統所有連線的keepalive引數:

view plaincopy to clipboardprint?
[HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesTcpipParameters]  
“KeepAliveTime”=dword:006ddd00  
“KeepAliveInterval”=dword:000003e8  
“MaxDataRetries”=”5″  
 
 
對於實用的程式來說,2小時的空閒時間太長。因此,我們需要手工開啟Keepalive功能並設定合理的Keepalive引數。

view plaincopy to clipboardprint?
// 開啟KeepAlive  
BOOL bKeepAlive = TRUE;  
int nRet = ::setsockopt(socket_handle, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeepAlive, sizeof(bKeepAlive));  
if (nRet == SOCKET_ERROR)  
{  
return FALSE;  
}  
// 設定KeepAlive引數  
tcp_keepalive alive_in                = {0};  
tcp_keepalive alive_out                = {0};  
alive_in.keepalivetime                = 5000;                // 開始首次KeepAlive探測前的

TCP空閉時間  
alive_in.keepaliveinterval        = 1000;                // 兩次KeepAlive探測間的時間間隔  
alive_in.onoff                                = TRUE;  
unsigned long ulBytesReturn = 0;  
nRet = WSAIoctl(socket_handle, SIO_KEEPALIVE_VALS, &alive_in, sizeof(alive_in),  &alive_out, sizeof(alive_out), &ulBytesReturn, NULL, NULL);  
if (nRet == SOCKET_ERROR)  
{  
return FALSE;  
}  
 
 
開啟Keepalive選項之後,對於使用IOCP模型的伺服器端程式來說,一旦檢測到連線斷開,GetQueuedCompletionStatus函式將立即返回FALSE,使得伺服器端能及時清除該連線、釋放該連線相關的資源。對於使用select模型的客戶端來說,連線斷開被探測到時,以recv目的阻塞在socket上的select方法將立即返回SOCKET_ERROR,從而得知連線已失效,客戶端程式便有機會及時執行清除工作、提醒使用者或重新連線。

另一種技術,由應用程式自己傳送心跳包來檢測連線的健康性。客戶端可以在一個Timer中或低級別的執行緒中定時向發伺服器傳送一個短小精悍的包,並等待伺服器的迴應。客戶端程式在一定時間內沒有收到伺服器迴應即認為連線不可用,同樣,伺服器在一定時間內沒有收到客戶端的心跳包則認為客戶端已經掉線。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

windows下此處的”非正常斷開”指TCP連線不是以優雅的方式斷開,如網線故障等物理鏈路的原因,還有突然主機斷電等原因.

有兩種方法可以檢測:

1.TCP連線雙方定時發握手訊息

2.利用TCP協議棧中的KeepAlive探測
第二種方法簡單可靠,只需對TCP連線兩個Socket設定KeepAlive探測,
所以本文只講第二種方法在Linux,Window2000下的實現(在其它的平臺上沒有作進一步的測試)Windows 2000平臺下 標頭檔案

view plaincopy to clipboardprint?
 #include <mstcpip.h>  
//定義結構及巨集  
struct TCP_KEEPALIVE {  
u_longonoff;  
u_longkeepalivetime;  
u_longkeepaliveinterval;  
} ;  
tcp_keepalive live,liveout;    
live.keepaliveinterval=500;  
live.keepalivetime=3000;  
live.onoff=TRUE;    
int iRet = setsockopt(Socket,SOL_SOCKET,SO_KEEPALIVE,(char *)Opt,sizeof(int));    
if(iRet == 0){  
     DWORD dw;  
    if(WSAIoctl(Socket,SIO_KEEPALIVE_VALS,  
        &live,sizeof(live),&liveout,sizeof(liveout),  
        &dw,NULL,NULL)== SOCKET_ERROR){  
               //Delete Client    
               return;  
     }    
}   
 
 
 ACE下程式碼 //by rainfish    blog.csdn.net/bat603

view plaincopy to clipboardprint?
int Opt = 1;  
//在測試過程中,發現檢測的次數是5次,即下面的設定中,從最近一次訊息開始計算的10秒後,每次間隔5秒,連續傳送5次,即35秒發現網路斷了  
tcp_keepalive live,liveout;    
live.keepaliveinterval=5000; //每次檢測的間隔 (單位毫秒)  
live.keepalivetime=10000;  //第一次開始傳送的時間(單位毫秒)  
live.onoff=TRUE;    
int iRet = stream.set_option(SOL_SOCKET,SO_KEEPALIVE,&Opt,sizeof(int));    
if(iRet == 0){    
     DWORD dw;  
     //此處顯示了在ACE下獲取套接字的方法,即控制代碼的(SOCKET)化就是控制代碼  
    if(WSAIoctl((SOCKET)h,SIO_KEEPALIVE_VALS,&live,sizeof(live),  
        &liveout,sizeof(liveout),&dw,NULL,NULL)== SOCKET_ERROR){  
         //Delete Client    
         return;    
     }    
}   
 
 
Linux平臺下

view plaincopy to clipboardprint?
#include    "/usr/include/linux/tcp.h"  
#include "/usr/include/linux/socket.h"  
////KeepAlive實現,單位秒  
//下面程式碼要求有ACE,如果沒有包含ACE,則請把用到的ACE函式改成linux相應的介面  
int keepAlive = 1;//設定KeepAlive  
int keepIdle = 5;//開始首次KeepAlive探測前的TCP空閉時間  
int keepInterval = 5;//兩次KeepAlive探測間的時間間隔  
int keepCount = 3;//判定斷開前的KeepAlive探測次數  
if(setsockopt(s,SOL_SOCKET,SO_KEEPALIVE,(void*)&keepAlive,sizeof(keepAlive)) == -1)  
{  
    ACE_DEBUG ((LM_INFO,  
    ACE_TEXT ("(%P|%t) setsockopt SO_KEEPALIVE error!n")));  
}  
if(setsockopt(s,SOL_TCP,TCP_KEEPIDLE,(void *)&keepIdle,sizeof(keepIdle)) == -1)  
{  
    ACE_DEBUG ((LM_INFO,  
    ACE_TEXT ("(%P|%t) setsockopt TCP_KEEPIDLE error!n")));  
}  
if(setsockopt(s,SOL_TCP,TCP_KEEPINTVL,(void *)&keepInterval,sizeof(keepInterval)) == -1)  
{  
    ACE_DEBUG ((LM_INFO,  
    ACE_TEXT ("(%P|%t) setsockopt TCP_KEEPINTVL error!n")));  
}  
if(setsockopt(s,SOL_TCP,TCP_KEEPCNT,(void *)&keepCount,sizeof(keepCount)) == -1)  
{  
    ACE_DEBUG ((LM_INFO,  
    ACE_TEXT ("(%P|%t)setsockopt TCP_KEEPCNT error!n")));  
}  

百度文庫:

如何新增keepalive(已收藏在百度文庫)