TCP keepalive机制

首先,我想说的是,SO_Keeplive是实现在服务器侧,客户端被动响应,缺省超时时间为120分钟,这是RFC协议标准规范。SO_Keeplive是实现在TCP协议栈(四层),应用层的心跳实现在第七层,本质没有任何区别,但应用层需要自己来定义心跳包格式。之所以实现在服务器侧,是因为与客户端相比,服务器侧的寿命更长,因为服务器侧需要不间断地提供服务,而客户端可能由于用户下班而合上电脑(TCP没有来得及发送FIN关闭连接),这样的话,服务器侧就会有很多不可用的TCP连接(established),这样的连接依然会占用服务器内存资源,于是就设计这个keepalive 来检测客户端是否可用,如果几次重传keepalive ,客户端没有相应,删除连接,释放资源。需要指出的是,超时时间是指TCP连接没有任何数据、控制字传输的时间,如果有任何数据传输,会刷新定时器,重新走表。TCP心跳是一个备受争议的实现,只是一个option,不是强制标准。之所以应用层需要独立实现自己的心跳,是因为超时时间较长,无法给应用层提供快速的反馈。所以类似BGP协议就独立实现了自己的keepalive,最小可以设置一秒钟,三次没有应答即可以Reset连接,最快三秒可以检测到失效。而三秒依然太慢,可以用另外一个协议BFD来提供更快发现链路失效,最快可以配置成10ms,三次超时(30ms)就可以完成失效检测。

通过上面的介绍,感觉TCP keepalive已经很牛逼了,但为什么还会提到应用层的心跳呢?

目前了解的原因包括两个:

TCP keepalive处于传输层,由操作系统负责,能够判断进程存在,网络通畅,但无法判断进程阻塞或死锁等问题。
客户端与服务器之间有四层代理或负载均衡,即在传输层之上的代理,只有传输层以上的数据才被转发,例如socks5等
所以,基于以上原因,有时候还是需要应用程序自己去设计心跳规则的。
可以服务端负责周期发送心跳包,检测客户端,也可以客户端负责发送心跳包,或者服服务端和客户端同时发送心跳包。

可以根据具体的应用场景进行设计。

//代码层面的设置步骤:

int keepAlive = 1;    // 非0值,开启keepalive属性

int keepIdle = 60;    // 如该连接在60秒内没有任何数据往来,则进行此TCP层的探测

int keepInterval = 5; // 探测发包间隔为5秒

int keepCount = 3;        // 尝试探测的最多次数

// 开启探活
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));

setsockopt(sockfd, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));

setsockopt(sockfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));

setsockopt(sockfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount) 

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
存在下面两种情形:
情形一:一个客户端连接服务器以后,如果长期没有和服务器有数据来往,可能会被防火墙程序关闭连接,有时候我们并不想要被关闭连接。例如,对于一个即时通讯软件,如果服务器没有消息时,我们确实不会和服务器有任何数据交换,但是如果连接被关闭了,有新消息来时,我们再也没法收到了,这就违背了“即时通讯”的设计要求。

情形二:通常情况下,服务器与某个客户端一般不是位于同一个网络,其之间可能经过数个路由器和交换机,如果其中某个必经路由器或者交换器出现了故障,并且一段时间内没有恢复,导致这之间的链路不再畅通,而此时服务器与客户端之间也没有数据进行交换,由于 TCP 连接是状态机,对于这种情况,无论是客户端或者服务器都无法感知与对方的连接是否正常,这类连接我们一般称之为“死链”。

情形一中的应用场景要求必须保持客户端与服务器之间的连接正常,就是我们通常所说的“保活“。如上文所述,当服务器与客户端一定时间内没有有效业务数据来往时,我们只需要给对端发送心跳包即可实现保活。

情形二中的死链,只要我们此时任意一端给对端发送一个数据包即可检测链路是否正常,这类数据包我们也称之为”心跳包”,这种操作我们称之为“心跳检测”。顾名思义,如果一个人没有心跳了,可能已经死亡了;一个连接长时间没有正常数据来往,也没有心跳包来往,就可以认为这个连接已经不存在,为了节约服务器连接资源,我们可以通过关闭 socket,回收连接资源。

根据上面的分析,让我再强调一下,心跳检测一般有两个作用:

保活
检测死链

TCP keepalive 选项

操作系统的 TCP/IP 协议栈其实提供了这个的功能,即 keepalive 选项。在 Linux 操作系统中,我们可以通过代码启用一个 socket 的心跳检测(即每隔一定时间间隔发送一个心跳检测包给对端),代码如下:

//on 是 1 表示打开 keepalive 选项,为 0 表示关闭,0 是默认值
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on));

但是,即使开启了这个选项,这个选项默认发送心跳检测数据包的时间间隔是 7200 秒(2 小时),这时间间隔实在是太长了,不具有实用性。

我们可以通过继续设置 keepalive 相关的三个选项来改变这个时间间隔,它们分别是 TCP_KEEPIDLE、TCP_KEEPINTVL 和 TCP_KEEPCNT,示例代码如下:

//发送 keepalive 报文的时间间隔
int val = 7200;
setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val));

//两次重试报文的时间间隔
int interval = 75;
setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));

int cnt = 9;
setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));

TCP_KEEPIDLE 选项设置了发送 keepalive 报文的时间间隔,发送时如果对端回复 ACK。则本端 TCP 协议栈认为该连接依然存活,继续等 7200 秒后再发送 keepalive 报文;如果对端回复 RESET,说明对端进程已经重启,本端的应用程序应该关闭该连接。

如果对端没有任何回复,则本端做重试,如果重试 9 次(TCP_KEEPCNT 值)(前后重试间隔为 75 秒(TCP_KEEPINTVL 值))仍然不可达,则向应用程序返回 ETIMEOUT(无任何应答)或 EHOST 错误信息。

我们可以使用如下命令查看 Linux 系统上的上述三个值的设置情况:

[root@iZ238vnojlyZ ~]# sysctl -a | grep keepalive
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 7200
发布了46 篇原创文章 · 获赞 0 · 访问量 437

猜你喜欢

转载自blog.csdn.net/weixin_42226134/article/details/104374240
今日推荐