现在的位置: 首页 > 综合 > 正文

tcp keepalive

2014年03月14日 ⁄ 综合 ⁄ 共 3170字 ⁄ 字号 评论关闭

 部分信息可以看UNIX网络编程第157页,

摘录过来:

在一个正常的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操作的一方就会因为没有任何连接中断的通知而一直等待下去,也就是会被长时间卡住。这种情形解决的办法:

1>自己编写心跳包程序

简单的说也就是在自己的程序中加入一条线程,定时向对端发送数据包,查看是否有ACK,如果有则连接正常,没有的话则连接断开

2>启动TCP编程里的keepAlive机制

 

其实keepalive的原理就是TCP内嵌的一个心跳包,

以服务器端为例,如果当前server端检测到超过一定时间(默认是 7,200,000 milliseconds,也就是2个小时)没有数据传输,那么会向client端发送一个keep-alive
packet
(该keep-alive packet就是ACK和当前TCP序列号减一的组合),此时client端应该为以下三种情况之一:

 

1. client端仍然存在,网络连接状况良好。此时client端会返回一个ACKserver端接收到ACK后重置计时器,在2小时后再发送探测。如果2小时内连接上有数据传输,那么在该时间基础上向后推延2个小时。

2.
客户端异常关闭,或是网络断开。在这两种情况下,client端都不会响应。服务器没有收到对其发出探测的响应,并且在一定时间(系统默认为1000 ms)后重复发送keep-alive
packet
,并且重复发送一定次数(2000 XP 2003
系统默认为5, Vista后的系统默认为10次)。

3.
客户端曾经崩溃,但已经重启。这种情况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引起服务器对连接的终止。

 

  对于实用程序来说,2小时的空闲时间太长。因此,我们需要手工开启Keepalive功能并设置合理的Keepalive参数。在XP和WIN2003系统上,可以针对单独的socket来设置,但是在windows 2000,不能单独设置,如果设置,那么影响是整个系统的所有socket。

 

了解了keep alive大致的原理,下来看看在程序中怎么用,怎么设置参数:

#include <mstcpip.h>

		BOOL bKeepAlive = TRUE;
		int nRet = setsockopt(m_socket, 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(m_socket, 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上的说法,这些参数也可以通过在注册表里设置,分别为:

HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\KeepAliveTime

HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\KeepAliveInterval

 

另外,有些人可能已经发现了,tcp_keepalive这个结构体中没有对重试次数这个参数的设置,这个参数可以通过注册表来设置,具体位置为:

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

此处的keepalivetime表示的是TCP连接处于畅通时候的探测频率,一旦探测包没有返回,就以keepaliveinterval的频率发送,经过若干次的重试,如果探测包都没有返回,那么就得出结论:TCP连接已经断开,于是上面的Recv或Send调用也就能马上返回,不会无限制地卡住了。

  上图是对上面文字的说明。亮条之前,TCP处于畅通状态,KeepAlive是以1000毫秒(keepalivetime的值)的频率发送探测包,在发送到第32个探测包的时候,探测包没有返回,于是就以5000毫秒(keepalivetime的值)的频率发送探测包,重发几次后,探测包都没有返回,于是就得出结论:此TCP连接已经断开了!

 

设置好keepalive以后,我们通过实验来看看当client异常退出或是网络断掉的情况下,keepalive怎么通知我们异常断开的情况。这里采用select模式,实验环境为XP系统和Win7系统,几种情况返回值如下:

 

1. 正常断开

select函数正常返回,recv函数返回0

 

2. 异常断开

a)       程序异常退出,如client端重启,应用非正常关闭等

select函数正常返回,recv函数返回SOCKET_ERRORWSAGetLastError()得到的结果为WSAECONNRESET(10054)。

b)      网络断开

结果同上:select函数正常返回,recv函数返回SOCKET_ERRORWSAGetLastError()得到的结果为WSAECONNRESET(10054)

对于程序异常退出的情况,实际上在不开启keepalive的情况下也是可以检测到的

 

 

抱歉!评论已关闭.