找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 2483|回復(fù): 0
收起左側(cè)

TCP連接探測中的Keepalive 和心跳包

[復(fù)制鏈接]
ID:107189 發(fā)表于 2016-3-5 23:19 | 顯示全部樓層 |閱讀模式
TCP連接探測中的Keepalive 和心跳包 收藏
采用TCP連接的C/S模式軟件,連接的雙方在連接空閑狀態(tài)時(shí),如果任意一方意外崩潰、當(dāng)機(jī)、網(wǎng)線斷開或路由器故障,另一方無法得知TCP連接已經(jīng)失效,除非繼續(xù)在此連接上發(fā)送數(shù)據(jù)導(dǎo)致錯(cuò)誤返回。很多時(shí)候,這不是我們需要的。我們希望服務(wù)器端和客戶端都能及時(shí)有效地檢測到連接失效,然后優(yōu)雅地完成一些清理工作并把錯(cuò)誤報(bào)告給用戶。
如何及時(shí)有效地檢測到一方的非正常斷開,一直有兩種技術(shù)可以運(yùn)用。一種是由TCP協(xié)議層實(shí)現(xiàn)的Keepalive,另一種是由應(yīng)用層自己實(shí)現(xiàn)的心跳包。
TCP默認(rèn)并不開啟Keepalive功能,因?yàn)殚_啟Keepalive功能需要消耗額外的寬帶和流量,盡管這微不足道,但在按流量計(jì)費(fèi)的環(huán)境下增加了費(fèi)用,另一方面,Keepalive設(shè)置不合理時(shí)可能會因?yàn)槎虝旱木W(wǎng)絡(luò)波動(dòng)而斷開健康的TCP連接。并且,默認(rèn)的Keepalive超時(shí)需要7,200,000 milliseconds,即2小時(shí),探測次數(shù)為5次。
對于Win2K/XP/2003,可以從下面的注冊表項(xiàng)找到影響整個(gè)系統(tǒng)所有連接的keepalive參數(shù):

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]

“KeepAliveTime”=dword:006ddd00

“KeepAliveInterval”=dword:000003e8

“MaxDataRetries”=”5″

對于實(shí)用的程序來說,2小時(shí)的空閑時(shí)間太長。因此,我們需要手工開啟Keepalive功能并設(shè)置合理的Keepalive參數(shù)。

// 開啟KeepAlive

BOOL bKeepAlive = TRUE;

int nRet = ::setsockopt(socket_handle, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeepAlive, sizeof(bKeepAlive));

if (nRet == SOCKET_ERROR)

{

return FALSE;

}

// 設(shè)置KeepAlive參數(shù)

tcp_keepalive alive_in                = {0};

tcp_keepalive alive_out                = {0};

alive_in.keepalivetime                = 5000;                // 開始首次KeepAlive探測前的TCP空閉時(shí)間

alive_in.keepaliveinterval        = 1000;                // 兩次KeepAlive探測間的時(shí)間間隔

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選項(xiàng)之后,對于使用IOCP模型的服務(wù)器端程序來說,一旦檢測到連接斷開,GetQueuedCompletionStatus函數(shù)將立即返回FALSE,使得服務(wù)器端能及時(shí)清除該連接、釋放該連接相關(guān)的資源。對于使用select模型的客戶端來說,連接斷開被探測到時(shí),以recv目的阻塞在socket上的select方法將立即返回SOCKET_ERROR,從而得知連接已失效,客戶端程序便有機(jī)會及時(shí)執(zhí)行清除工作、提醒用戶或重新連接。

另一種技術(shù),由應(yīng)用程序自己發(fā)送心跳包來檢測連接的健康性?蛻舳丝梢栽谝粋(gè)Timer中或低級別的線程中定時(shí)向發(fā)服務(wù)器發(fā)送一個(gè)短小精悍的包,并等待服務(wù)器的回應(yīng)?蛻舳顺绦蛟谝欢〞r(shí)間內(nèi)沒有收到服務(wù)器回應(yīng)即認(rèn)為連接不可用,同樣,服務(wù)器在一定時(shí)間內(nèi)沒有收到客戶端的心跳包則認(rèn)為客戶端已經(jīng)掉線。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 windows下此處的”非正常斷開”指TCP連接不是以優(yōu)雅的方式斷開,如網(wǎng)線故障等物理鏈路的原因,還有突然主機(jī)斷電等原因.
有兩種方法可以檢測:
1.TCP連接雙方定時(shí)發(fā)握手消息

2.利用TCP協(xié)議棧中的KeepAlive探測
第二種方法簡單可靠,只需對TCP連接兩個(gè)Socket設(shè)定KeepAlive探測,
所以本文只講第二種方法在Linux,Window2000下的實(shí)現(xiàn)(在其它的平臺上沒有作進(jìn)一步的測試)
Windows 2000平臺下 頭文件

#include <mstcpip.h>
//定義結(jié)構(gòu)及宏
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
int Opt = 1;
//在測試過程中,發(fā)現(xiàn)檢測的次數(shù)是5次,即下面的設(shè)置中,從最近一次消息開始計(jì)算的10秒后,每次間隔5秒,連續(xù)發(fā)送5次,即35秒發(fā)現(xiàn)網(wǎng)絡(luò)斷了
tcp_keepalive live,liveout;  
live.keepaliveinterval=5000; //每次檢測的間隔 (單位毫秒)
live.keepalivetime=10000;  //第一次開始發(fā)送的時(shí)間(單位毫秒)
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平臺下

#include    "/usr/include/linux/tcp.h"
#include "/usr/include/linux/socket.h"
////KeepAlive實(shí)現(xiàn),單位秒
//下面代碼要求有ACE,如果沒有包含ACE,則請把用到的ACE函數(shù)改成linux相應(yīng)的接口
int keepAlive = 1;//設(shè)定KeepAlive
int keepIdle = 5;//開始首次KeepAlive探測前的TCP空閉時(shí)間
int keepInterval = 5;//兩次KeepAlive探測間的時(shí)間間隔
int keepCount = 3;//判定斷開前的KeepAlive探測次數(shù)
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")));
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

回復(fù)

使用道具 舉報(bào)

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表