TCP保活机制(KeepAlive)

TCP保活机制

如果两端的 TCP 连接一直没有数据交互,达到了触发 TCP 保活机制的条件,那么内核里的 TCP 协议栈就会发送探测报文。

  • 如果对端程序是正常工作的。当 TCP 保活的探测报文发送给对端, 对端会正常响应,这样 TCP 保活时间会被重置,等待下一个 TCP 保活时间的到来。

  • 如果对端主机崩溃,或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP 会报告该 TCP 连接已经死亡

所以,TCP 保活机制可以在双方没有数据交互的情况,通过探测报文,来确定对方的 TCP 连接是否存活。

TCP保活相关参数如下:

  • SO_KEEPALIVE:是否开启保活

  • TCP_KEEPIDLE:Start keeplives after this period

  • TCP_KEEPINTVL:Interval between keepalives

  • TCP_KEEPCNT:Number of keepalives before death

保活机制示例

开启一个简单的tcp服务器,代码如下

/*server.c*/
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include <netinet/tcp.h>
​
#define PORT 4000//端口号 
#define BACKLOG 5/*最大监听数*/ 
#define MAX_DATA 100//接收到的数据最大程度 
​
int main(){
    int sockfd,new_fd;/*socket句柄和建立连接后的句柄*/
    struct sockaddr_in my_addr;/*本方地址信息结构体,下面有具体的属性赋值*/
    struct sockaddr_in their_addr;/*对方地址信息*/
    int sin_size;
    char buf[MAX_DATA];//储存接收数据 
​
    sockfd=socket(AF_INET,SOCK_STREAM,0);//建立socket 
    if(sockfd==-1){
        printf("socket failed:%d",errno);
        return -1;
    }
    my_addr.sin_family=AF_INET;/*该属性表示接收本机或其他机器传输*/
    my_addr.sin_port=htons(PORT);/*端口号*/
    my_addr.sin_addr.s_addr=htonl(INADDR_ANY);/*IP,括号内容表示本机IP*/
    bzero(&(my_addr.sin_zero),8);/*将其他属性置0*/
    if(bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr))<0){//绑定地址结构体和socket
        printf("bind error");
        return -1;
    }
    listen(sockfd,BACKLOG);//开启监听 ,第二个参数是最大监听数 
    while(1){
        sin_size=sizeof(struct sockaddr_in);
        new_fd=accept(sockfd,(struct sockaddr*)&their_addr,&sin_size);//在这里阻塞知道接收到消息,参数分别是socket句柄,接收到的地址信息以及大小 
        // 开启保活,1分钟内探测不到,断开连接
        int keep_alive = 1;
        int keep_idle = 3;
        int keep_interval = 1;
        int keep_count = 57;
        if (setsockopt(new_fd, SOL_SOCKET, SO_KEEPALIVE, &keep_alive, sizeof(keep_alive))) {
            perror("Error setsockopt(SO_KEEPALIVE) failed");
            exit(1);
        }
        if (setsockopt(new_fd, IPPROTO_TCP, TCP_KEEPIDLE, &keep_idle, sizeof(keep_idle))) {
            perror("Error setsockopt(TCP_KEEPIDLE) failed");
            exit(1);
        }
        if (setsockopt(new_fd, SOL_TCP, TCP_KEEPINTVL, (void *)&keep_interval, sizeof(keep_interval))) {
            perror("Error setsockopt(TCP_KEEPINTVL) failed");
            exit(1);
        }
        if (setsockopt(new_fd, SOL_TCP, TCP_KEEPCNT, (void *)&keep_count, sizeof(keep_count))) {
            perror("Error setsockopt(TCP_KEEPCNT) failed");
            exit(1);
        }
        while(new_fd != -1) {
            recv(new_fd,buf,MAX_DATA,0);//将接收数据打入buf,参数分别是句柄,储存处,最大长度,其他信息(设为0即可)。 
            printf("%s",buf);
        }
    }
    return 0;
} 

上述保活参数(int keep_alive = 1;int keep_idle = 3;int keep_interval = 1;int keep_count = 57;)表示3秒内无交互后,每隔1秒检测一次,57次都没得到响应时会断开连接。

编译可执行文件:gcc server.c -o server

服务端开启服务:./server

客户端连接服务:telnet 192.168.137.101 4000

这里服务端地址为192.168.137.101

《TCP保活机制(KeepAlive)》

上图可以看到每隔1秒有保活报文,且客户端正常响应。

接下来我们将192.168.137.102的网卡down掉

《TCP保活机制(KeepAlive)》可以看到客户端已经无响应了,经过1分种左右就可看见服务端的连接断掉了

《TCP保活机制(KeepAlive)》

 

TCP异常断开连接场景

几个思考点:

是否开启keepalive?

是否存在数据交互?

进程崩溃;

客户端主机宕机(客户端网络不可用 是同种情况);

keepalive开启,服务器会探测客户端,不会出现问题。

keepalive未开启,存在以下情况

1、客户端进程崩溃

使用 kill -9 来模拟进程崩溃的情况,发现在 kill 掉进程后,服务端会发送 FIN 报文,与客户端进行四次挥手

所以,即使没有开启 TCP keepalive,且双方也没有数据交互的情况下,如果其中一方的进程发生了崩溃,这个过程操作系统是可以感知的到的,于是就会发送 FIN 报文给对方,然后与对方进行 TCP 四次挥手。

2、存在数据交互,客户端主机宕机

  • 第一种,客户端主机宕机,又迅速重启,会发生什么?

  • 第二种,客户端主机宕机,一直没有重启,会发生什么?

客户端主机宕机,又迅速重启

在客户端主机宕机后,服务端向客户端发送的报文会得不到任何的响应,在一定时长后,服务端就会触发超时重传机制,重传未得到响应的报文。

服务端重传报文的过程中,刚好客户端主机重启完成,这时客户端的内核就会接收重传的报文,:

  • 如果客户端主机上没有进程监听该 TCP 报文的目标端口号,由于找不到目标端口,客户端内核就会回复 RST 报文,重置该 TCP 连接

  • 如果客户端主机上进程监听该 TCP 报文的目标端口号,由于客户端主机重启后,之前的 TCP 连接的数据结构已经丢失了,客户端内核里协议栈会发现找不到该 TCP 连接的 socket 结构体,于是就会回复 RST 报文,重置该 TCP 连接。

所以,只要有一方重启完成后,收到之前 TCP 连接的报文,都会回复 RST 报文,以断开连接。

客户端主机宕机,一直没有重启

这种情况,服务端超时重传报文的次数达到一定阈值后,内核就会判定出该 TCP 有问题,然后通过 Socket 接口告诉应用程序该 TCP 连接出问题了。

那具体重传几次呢?

在 Linux 系统中,提供了一个叫 tcp_retries2 配置项,默认值是 15,如下:

[root@node01 ~]# cat /proc/sys/net/ipv4/tcp_retries2
15

这个内核参数是控制,在 TCP 连接建立的情况下,超时重传的最大次数。

不过 tcp_retries2 设置了 15 次,并不代表 TCP 超时重传了 15 次才会通知应用程序终止该 TCP 连接,内核还会基于「最大超时时间」来判定。

每一轮的超时时间都是倍数增长的,比如第一次触发超时重传是在 2s 后,第二次则是在 4s 后,第三次则是 8s 后,以此类推。

内核会根据 tcp_retries2 设置的值,计算出一个最大超时时间。

在重传报文且一直没有收到对方响应的情况时,先达到「最大重传次数」或者「最大超时时间」这两个的其中一个条件后,就会停止重传

3、不存在数据交互,客户端主机宕机

如果客户端主机崩溃了,服务端是无法感知到的,在加上服务端没有开启 TCP keepalive,又没有数据交互的情况下,服务端的 TCP 连接将会一直处于 ESTABLISHED 连接状态,直到服务端重启进程。

在没有使用 TCP 保活机制,且双方不传输数据的情况下,一方的 TCP 连接处在 ESTABLISHED 状态时,并不代表另一方的 TCP 连接还一定是正常的。

    原文作者:白乐先
    原文地址: https://blog.csdn.net/ComplexMaze/article/details/124201088
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞