TCP keepalive机制最初是为了解决长时间处于空闲状态的连接问题而设计的。
在早期的TCP实现中,如果连接处于空闲状态,TCP协议不会发送任何数据包,这可能会导致网络中的路由器或防火墙关闭连接。
为了解决这个问题,TCP keepalive机制被引入到TCP协议中,它可以定期发送一些探测包来保持连接的活跃状态,从而避免连接被关闭。
当然,还有一种作用是:检测连接是否仍然处于活动状态。如果对端没有响应,就会认为服务端故障,可以及时关闭连接。
操作系统设置tcp协议keep alive参数主要为以下三个文件:
$ ll /proc/sys/net/ipv4/tcp_keepalive*
-rw-r--r-- 1 root root 0 May 6 16:59 /proc/sys/net/ipv4/tcp_keepalive_intvl
-rw-r--r-- 1 root root 0 May 9 16:31 /proc/sys/net/ipv4/tcp_keepalive_probes
-rw-r--r-- 1 root root 0 May 6 16:59 /proc/sys/net/ipv4/tcp_keepalive_time
三个文件默认的值分别为:
$ cat /proc/sys/net/ipv4/tcp_keepalive*
75
9
7200
这三个文件的意义为:
这些值无法通过vim进行修改,如果想要修改这些值,可以使用echo,例如:
echo 600 > /proc/sys/net/ipv4/tcp_keepalive_time
会将7500改为600。
上面的配置只是操作系统默认的TCP keepalive属性,实际上,TCP keepalive属性是可以通过套接字选项进行配置的。在实际进行通信时,我们需要查看具体的套接字属性,而不是仅仅依赖于操作系统的默认设置。
通过下面的程序,我们可以看到,在目前实验的机器上,默认的套接字keepalive属性是关闭的,并且套接字的属性是操作系统的TCP属性。当更改操作系统的TCP keepalive属性时,套接字的属性也会随之变动。
下面是一个示例程序,用于查看套接字的TCP keepalive属性:
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
int main(void);
int main() {
int s;
int optval;
socklen_t optlen = sizeof(optval);
/* Create the socket */
if ((s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror("socket()");
exit(EXIT_FAILURE);
}
/* Check the status for the keepalive option */
if (getsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &optval, &optlen) < 0) {
perror("getsockopt()");
close(s);
exit(EXIT_FAILURE);
}
printf("default SO_KEEPALIVE setting is %s\n", (optval ? "ON" : "OFF"));
/* Set the option active */
optval = 1;
optlen = sizeof(optval);
if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) {
perror("setsockopt()");
close(s);
exit(EXIT_FAILURE);
}
// printf("SO_KEEPALIVE set on socket\n");
/* Check the status again */
if (getsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &optval, &optlen) < 0) {
perror("getsockopt()");
close(s);
exit(EXIT_FAILURE);
}
printf("now SO_KEEPALIVE setting is %s\n", (optval ? "ON" : "OFF"));
if (getsockopt(s, IPPROTO_TCP, TCP_KEEPCNT, &optval, &optlen) < 0) {
perror("setsockopt()");
exit(EXIT_FAILURE);
}
printf("default TCP_KEEPCNT val %d\n", optval);
if (getsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE, &optval, &optlen) < 0) {
perror("setsockopt()");
exit(EXIT_FAILURE);
}
printf("default TCP_KEEPIDLE val %d\n", optval);
if (getsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL, &optval, &optlen) < 0) {
perror("setsockopt()");
exit(EXIT_FAILURE);
}
printf("default TCP_KEEPINTVL val %d\n", optval);
close(s);
exit(EXIT_SUCCESS);
}
通过 gcc -o test test.cc && ./test 执行,运行结果入下:
default SO_KEEPALIVE setting is OFF
now SO_KEEPALIVE setting is ON
default TCP_KEEPCNT val 9
default TCP_KEEPIDLE val 7200
default TCP_KEEPINTVL val 75
如果按照第二章内容重设系统keep alive属性重新执行,会发现输出值也会相应变化。
下面我们编写一个TCP客户端和服务端,并使用抓包工具来查看TCP keepalive包的状态。我们将在客户端打开TCP keepalive,并设置探测包发送间隔为3秒,最大探测10次,探测间隔10秒。然后,在客户端与服务端建立连接之后,我们将关闭服务端,并使用抓包工具来查看TCP keepalive包的状态。
抓包结果如下:
从图上可以看到,虽然我们关闭了server进程,但是client还是一直能收到8083端口的keep-alive ack回包,这是由于TCP keepalive ACK包是表明:服务端的操作系统内核仍然处于活动状态,而不是表明服务器仍然处于正常运行状态。如果服务器已经被杀掉或者出现了故障,TCP keepalive ACK回包仍然会被发送,换句话说,keepalive这个特性不会被用户态的我们感知到,这是完全作用在内核层面的东西。
这次我们希望模拟网络不通的情况,思路是,用自己的linux客户端程序访问我自己mac电脑上的服务端程序,建立连接后,关闭mac的WiFi,完整的抓包结果如下:
可以看到:
这个就和我设置的参数完全对应了。
首先,我们介绍了TCP keepalive的背景和作用,包括保持连接的活跃状态、检测网络故障和服务器故障。然后,我们讨论了TCP keepalive的配置和调整,包括操作系统默认的TCP keepalive属性和套接字的TCP keepalive属性。最后,我们编写了一个TCP客户端和服务端,并使用抓包工具来查看TCP keepalive包的状态,以帮助我们更好地理解TCP keepalive机制的工作原理。
在某些场景下keep alive特性会非常重要,例如,ftp协议中,一次任务会建立两个tcp连接,一个用于传输,一个用于控制(比如控制上传哪个文件,中断传输等),在某些极端场景下,比如你要上传一个100G的文件,传输时间可能会好几个小时,这时,如果你用于控制的tcp连接被断掉了,那么可能你长久以来的努力都白费了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。