TCP长连接(KeepAlive)

转自:https://blog.csdn.net/chrisnotfound/article/details/80111559

1. TCP Keepalive的起源

TCP协议中有长连接和短连接之分。
短连接环境下,数据交互完毕后,主动释放连接;
长连接的环境下,进行一次数据交互后,很长一段时间内无数据交互时,客户端可能意外断电、死机、崩溃、重启,还是中间路由网络无故断开,这些TCP连接并未来得及正常释放,那么,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,且有可能导致在一个无效的数据链路层面发送业务数据,结果就是发送失败。所以服务器端要做到快速感知失败,减少无效链接操作,这就有了TCP的Keepalive(保活探测)机制。

2. TCP Keepalive工作原理

当一个 TCP 连接建立之后,启用 TCP Keepalive 的一端便会启动一个计时器,当这个计时器数值到达 0 之后(也就是经过tcp_keep-alive_time时间后),一个 TCP 探测包便会被发出。这个 TCP 探测包是一个纯 ACK 包(规范建议,不应该包含任何数据,但也可以包含1个无意义的字节,比如0x0。),其 Seq号 与上一个包是重复的,所以其实探测包报文不在窗口控制范围内。

如果一个给定的连接在两小时内(默认时长)没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于以下4个状态之一:

  1. 客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。

  2. 客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。

  3. 客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。

  4. 客户机正常运行,但是服务器不可达,这种情况与2类似,TCP能发现的就是没有收到探测的响应。

对于linux内核来说,应用程序若想使用TCP Keepalive,需要设置SO_KEEPALIVE套接字选项才能生效。

三个重要的参数

  1. tcp_keepalive_time,在TCP保活打开的情况下,最后一次数据交换到TCP发送第一个保活探测包的间隔,即允许的持续空闲时长,或者说每次正常发送心跳的周期,默认值为7200s(2h)。

  2. tcp_keepalive_probes 在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包次数,默认值为9(次)

  3. tcp_keepalive_intvl,在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包的发送频率,默认值为75s。

其他编程语言有相应的设置方法,这里只谈linux内核参数的配置。例如C语言中的setsockopt()函数,java的Netty服务器框架中也提供了相关接口。

3. TCP Keepalive作用

① 探测连接的对方是否存活
在应用交互的过程中,可能存在以下几种情况:
(1)客户端或服务器意外断电,死机,崩溃,重启。
(2)中间网络已经中断,而客户端与服务器并不知道。
利用保活探测功能,可以探知这种对端的意外情况,从而保证在意外发生时,可以释放半打开的TCP连接。
② 防止中间设备因超时删除连接相关的连接表
中间设备如防火墙等,会为经过它的数据报文建立相关的连接信息表,并为其设置一个超时时间的定时器,如果超出预定时间,某连接无任何报文交互的话,
中间设备会将该连接信息从表中删除,在删除后,再有应用报文过来时,中间设备将丢弃该报文,从而导致应用出现异常。

4. 使用方法

结构体使用

struct tcp_keepalive {
	ULONG onoff ;//是否开启 keepalive
	ULONG keepalivetime ; //多长时间(ms)没有数据就开始心跳包
	ULONG keepaliveinterval ; // 每隔多长时间(ms)发送一个心跳包,
};

这个结构体设置了空闲检测时间,及检测时重复发送的间隔时间。此外, tcp_keepalive 这个结构体中没有对重试次数这个参数的设置,这个参数可以通过注册表来设置,具体位置为:
HKLM/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters/TcpMaxDataRetransmissions

实际程序

	#include 
    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;
	}
	//设置tcp_keepalive参数
	tcp_keepalive alive_in;
	tcp_keepalive alive_out;
	alive_in.keepalivetime    = 500;  // 0.5s
	alive_in.keepaliveinterval  = 1000; //1s
	alive_in.onoff= 1;
	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 默认的参数可能不符合我们的要求,所以利用WSAIoctl函数通过 tcp_keepalive 结构体对这些参数进行了相应设置

你可能感兴趣的:(整理学习完整版)