TCP/IP学习笔记(五)TCP的保活定时器

正常情况下,TCP连接的终止需要经历四次挥手阶段,体现在代码上就是某一端主动调用close函数关闭套接字,随后TCP向对端发送FIN位被置为1的报文段标志着连接的结束,同时对端响应应答报文段,并在随后的某一时刻同样调用close函数,发送FIN报文段,当确认完成后就标志着TCP连接正常终止。

然而,考虑一种情况,在TCP连接建立成功后,客户端主机突然崩溃(断电,断网等),导致客户端的TCP还没来得及将FIN发送给服务器就已经关闭了(或者由于断网导致FIN无法到达对端),而服务器通常又不会主动发送数据给客户端。在这种情况下,客户端已经关闭,服务器却不知道,依然保持着维护一个连接应该有的所有东西(包括文件描述符,TCP缓冲区等),一个还好,当有大量这种半开连接存在于服务器中,会造成大量的浪费

保活定时器

在TCP协议中,有一个被称作保活定时器的组件,它就是为了解决上述问题被引进的。该定时器的原理也非常简单,当通信双方在定时器规定的这段时间内没有进行数据交互,那么开启保活选项的一端TCP就会发送一个探查报文段,当对端TCP接收到报文段发现这是个探查报文时,会响应应答信息。

上述过程是TCP独立于应用程序进行的,应用程序不会知道探查报文的发送和到达

此外,保活定时器的定时时间是两小时,是系统级别的参数,可以更改,但是最好不要更改,会影响到系统进程

如果一个连接在两小时内没有任何数据交互,则服务器就向客户端发送一个探查报文段(假设服务器开启保活定时器),此时客户端主机有四中可能的状态

  • 客户端主机依然正常运行,并从服务器可达。客户端TCP接收到探查报文段并返回应答报文段,此时服务器知道客户端依然存在,所以重置保活定时器。另外,如果在定时器溢出之前进行了数据交互,定时器也会被重置
  • 客户端主机已崩溃,已关闭或者正在重启。这种情况下,服务器无法得到客户端回应,探测报文段在75秒后超时,服务器总共发送10个这样的探测,每个间隔75秒(超时重传)。如果服务器还是没有接收到客户端的应答,就认为客户端已经关闭,随后告知应用程序,关闭连接(套接字变为可读,读取错误信息为“连接超时”)
  • 客户端主机崩溃并已重启。在这种情况下,由于客户端重启了系统,所有的TCP连接信息都已经丢失,当接收到服务器的探测报文段时,客户端在本机中无法找到匹配的TCP连接,随后发送一个复位报文段,服务器接收到复位报文段后告知应用程序,关闭连接(套接字变为可读,读取错误信息为“连接被重置”)
  • 客户端主机正常运行,但是从服务器不可达。这种情况通常是由于传输路径中的某个路由器关闭导致,此种情况与客户端主机崩溃并正在重启时处理方式相同,因为都是收不到应答

下面通过tcpdump抓包分析后三种情况,分析时假设崩溃的一方是服务器,随后观察客户端TCP探查报文段发送情况

另一端主机崩溃,已关闭或正在重启

借用<TCP/IP详解>中的示例,建立TCP连接后等待四小时后断开服务器以太网电缆,观察客户端探查报文段发送情况

观察tcpdump输出结果(省略了三次握手的报文段)

结果中前三行表示客户端发送”hello world”报文段,服务器对该报文段进行应答并回显”hello world”报文段,客户端对服务器的报文段进行应答

随后的四小时内,客户端先后两次尝试探查服务器是否关闭,首先看到的是一个ARP请求和ARP应答,随后发送探查报文段并得到回应

接下来断开服务器以太网电缆,并在两个小时之后又进行探查,然而由于服务器主机以太网断开,无法回应客户端ARP请求,随后的每75秒进行一次超时重传,最终放弃,错误码为“连接超时”

ARP请求主要用来获取对端的物理地址,首先需要知道对方地址,才能发送数据,而由于服务器以太网断开无法回应ARP请求,所以上述过程没有看到TCP报文的发送就已经可以确认对方崩溃

另一端崩溃并重新启动

借用<TCP/IP详解>中的示例,在建立连接后将服务器以太网断开,重新启动,然后再连接到网络上。由于服务器已重启,对于之前所有的TCP连接信息都已丢失,当接收到客户端发来的探查报文段时,会回复一个复位报文段

观察tcpdump输出结果(省去了连接建立过程)

可以看到,由于服务器以太网连接正常,ARP请求得到应答,随后客户端发送探查报文段,但是服务器已经重启,没有关于该连接的信息,只能返回一个复位报文段

另一端不可达

再次使用<TCP/IP详解>中的示例,连接建立后先确认可以正常通讯,等待两小时后发送探查报文并收到回应,随后将中间链路断开,观察结果

观察tcpdump输出结果(省略掉连接的建立过程)

前三行是正常的回显流程,4,5行是两小时后的探查报文,随后的若干行由于中间链路被断开,引发来自客户端主机路由器的ICMP不可达错误,随后又连续发送了9个探查报文,最终返回给应用程序“没有到达主机的路由”错误

SO_KEEPALIVE选项

linux内核中有关于保活定时器的三个参数

  • tcp_keepalive_time,指空闲时长,tcp_keepalive_time时间没有数据交互就发送探查报文段,默认7200s(2h)
  • tcp_keepalive_intvl,发送探查报文段后如果没有收到应答,tcp_keepalive_intvl时间后会重新发送,默认是75s
  • tcp_keepalive_probes,发送探查报文没有收到应答后继续发送的次数,发送tcp_keepalive_probes次后认为对方已关闭,默认是9次

如果要更改,可以在/etc/sysctl.conf文件中写入(值自己设置)

net.ipv4.tcp_keepalive_time = 
net.ipv4.tcp_keepalive_probes = 
net.ipv4.tcp_keepalive_intvl = 

保存后执行sysctl -p生效。这里是直接修改了内核参数,此后所有的进程的SO_KEEPALIVE时间都被更改

但是由于上述三个参数都是系统级别的参数,也就是说如果更改,那么就会影响开启了SO_KEEPALIVE的系统进程,可能造成意想不到的后果

如果只想在某个应用程序中修改时间,除了开启SO_KEEPALIVE选项外,需要同时设计三个TCP参数

#include <netinet/tcp.h>

/* fd : 套接字描述符
 * time : 空闲时间,当time时间没有数据交互的话就发送探查报文 */
int enableKeepAlive(int fd, int time)
{
    int val = 1;
    ::setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val));

    val = time;  //空闲时间
    ::setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val));

    val = time / 3; //探查报文的发送间隔
    ::setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val));

    val = 3; //探查报文的发送次数
    ::setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val));
}

猜你喜欢

转载自blog.csdn.net/sinat_35261315/article/details/79378456
今日推荐