1.TCP 四次挥手过程是怎样的?
1.客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
2.服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态。
3.客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
4.客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
5.服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
6.客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭
双方都可以主动断开连接,断开连接后主机中的「资源」将被释放
2.为什么挥手需要四次?
服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,因此是需要四次挥手
3.第一次挥手丢失了,会发生什么?
第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文
如果达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次挥手(ACK报文),那么客户端就会断开连接。
4.第二次挥手丢失了,会发生什么?
ACK 报文是不会重传的,所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数
1.对于 close 函数关闭的连接,由于无法再发送和接收数据,所以FIN_WAIT2 状态不可以持续太久,而 tcp_fin_timeout 控制了这个状态下连接的持续时长,默认值是 60 秒。
这对于调用 close 关闭的连接,如果在 60 秒后还没有收到 FIN 报文,客户端(主动关闭方)的连接就会直接关闭
2.如果主动关闭方使用 shutdown 函数关闭连接,指定了只关闭发送方向,而接收方向并没有关闭,那么意味着主动关闭方还是可以接收数据的。
如果主动关闭方一直没收到第三次挥手,那么主动关闭方的连接将会一直处于 FIN_WAIT2 状态(tcp_fin_timeout 无法控制 shutdown 关闭的连接)
5.第三次挥手丢失了,会发生什么?
服务端处于 CLOSE_WAIT 状态时,调用了 close 函数,内核就会发出 FIN 报文,同时连接进入 LAST_ACK 状态,等待客户端返回 ACK 来确认连接关闭,
1.当服务端重传第三次挥手报文的次数达到了重传最大次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK报文),那么服务端就会断开连接。
2.客户端因为是通过 close 函数关闭连接的,处于 FIN_WAIT_2 状态是有时长限制的,如果 tcp_fin_timeout 时间内还是没能收到服务端的第三次挥手(FIN 报文),那么客户端就会断开连接。
6.第四次挥手丢失了,会发生什么?
当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入 TIME_WAIT 状态
1.当服务端重传第三次挥手报文达到 最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK 报文),那么服务端就会断开连接。
2.客户端在收到第三次挥手后,就会进入 TIME_WAIT 状态,开启时长为 2MSL 的定时器,如果途中再次收到第三次挥手(FIN 报文)后,就会重置定时器,当等待 2MSL 时长后,客户端就会断开连接
7.为什么 TIME_WAIT 等待的时间是 2MSL?
MSL 是报文最大生存时间;它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃
2MSL时长 这其实是相当于至少允许报文丢失一次
网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间
比如,若 ACK 在一个 MSL 内丢失,这样被动方重发的 FIN 会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对
为什么不是 4 或者 8 MSL 的时长呢?
连续两次丢包的概率太小了,忽略它比解决它更具性价比
2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的
8.为什么需要 TIME_WAIT 状态?
主动发起关闭连接的一方,才会有 TIME-WAIT 状态。
需要 TIME-WAIT 状态,主要是两个原因:
1.防止历史连接中的数据,被后面相同四元组的连接错误的接收;
使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
2.保证「被动关闭连接」的一方,能被正确的关闭;
等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。
客户端在收到服务端重传的 FIN 报文时,TIME_WAIT 状态的等待时间,会重置回 2MSL。
序列号和初始化序列号并不是无限递增的,会发生回绕为初始值的情况,这意味着无法根据序列号来判断新老数据。
9.TIME_WAIT 过多有什么危害?
主要的危害有两种:
第一是占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等;
第二是占用端口资源,端口资源也是有限的,一般可以开启的端口为
32768~61000,也可以通过 net.ipv4.ip_local_port_range参数指定范围
如果客户端(主动发起关闭连接方)的 TIME_WAIT 状态过多
1.占满了所有端口资源,那么就无法对「目的 IP+ 目的 PORT」都一样的服务端发起连接了
2.但是被使用的端口,还是可以继续对另外一个服务端发起连接的,只要连接的是不同的服务端,端口是可以重复使用的
如果服务端(主动发起关闭连接方)的 TIME_WAIT 状态过多
因为服务端只监听一个端口,因此服务端可以建立很多连接
但是 TCP 连接过多,会占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等。
10.如何优化 TIME_WAIT?
1.打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项;
net.ipv4.tcp_tw_reuse = 1;可以复用处于 TIME_WAIT 的 socket 为新的连接所用
tcp_tw_reuse 功能只能用客户端(连接发起方),因为开启了该功能,在调用 connect() 函数时,内核会随机找一个
time_wait 状态超过 1 秒的连接给新的连接复用
2.net.ipv4.tcp_max_tw_buckets
当系统中处于 TIME_WAIT 的连接一旦超过这个值时,系统就会将后面的 TIME_WAIT 连接状态重置
3.程序中使用 SO_LINGER ,应用强制使用 RST 关闭
如果服务端要避免过多的 TIME_WAIT 状态的连接,就永远不要主动断开连接,让客户端去断开,由分布在各处的客户端去承受TIME_WAIT
11.服务器出现大量 TIME_WAIT 状态的原因有哪些?
TIME_WAIT 状态是主动关闭连接方才会出现的状态,所以如果服务器出现大量的 TIME_WAIT 状态的 TCP 连接,就是说明服务器主动断开了很多 TCP 连接
什么场景下服务端会主动断开连接呢?
第一个场景:HTTP 没有使用长连接
当服务端出现大量的 TIME_WAIT 状态连接的时候,可以排查下是否客户端和服务端都开启了 HTTP Keep-Alive
解决的方式是让客户端和服务端都开启 HTTP Keep-Alive 机制
第二个场景:HTTP 长连接超时
第三个场景:HTTP 长连接的请求数量达到上限
Web 服务端通常会有个参数,来定义一条 HTTP 长连接上最大能处理的请求数量,当超过最大限制时,就会主动关闭连接
12.服务器出现大量 CLOSE_WAIT 状态的原因有哪些?
CLOSE_WAIT 状态是「被动关闭方」才会有的状态,而且如果「被动关闭方」没有调用 close 函数关闭连接,那么就无法发出 FIN 报文,从而无法使得 CLOSE_WAIT 状态的连接转变为 LAST_ACK 状态。
当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。
通常需要排查代码
13.如果已经建立了连接,但是客户端突然出现故障了怎么办?
客户端出现故障指的是客户端的主机发生了宕机,或者断电的场景
TCP 有一个保活机制:
定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。
应用程序若想使用 TCP 保活机制需要通过 socket 接口设置 SO_KEEPALIVE 选项才能够生效
14.如果已经建立了连接,但是服务端的进程崩溃会发生什么?
TCP 的连接信息是由内核维护的
当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,
所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程。