TCP四次挥手
一、TCP四次挥手
第一次挥手
客户端打算关闭连接,此时会将TCP首部的FIN标志位标为1,将此报文发送给服务端,发送之后,客户端进入FIN_WAIT_1状态。
第二次挥手
服务端收到报文后,向客户端发送ACK应答报文,之后服务端进入CLOSED_WAIT状态。客户端收到ACK报文之后进入FIN_WAIT_2状态。
第三次挥手
等待服务端处理完数据,服务端向客户端发送FIN报文,之后服务端进入LAST_ACK状态。
第四次挥手
客户端收到服务端的FIN报文之后,给服务端发送一个ACK,之后进入TIME_WAIT状态。客户端在经过2MSL之后,自动进入CLOSED状态,到这里客户端完成连接的关闭。服务端在收到客户端的ACK应答报文之后,就进入了CLOSED状态,至此,服务端完成了连接的关闭。
主动关闭连接的一方才具有TIME_WAIT的状态,这点很重要。
二、四次回收的原因
- 关闭连接时,客户端向服务端发送FIN,仅仅表示客户端不再发送数据了但是还能接收数据。
- 服务端收到客户端的FIN报文后,先回一个应答报文,因为服务端可能还有数据需要处理和发送,等服务器不再发送数据时,才发送FIN报文给客户端表示同意现在关闭连接。
- 服务端通常要等待完成数据的发送和处理,所以服务端的ACK和FIN会分开发,从而比三次握手多了一次。
三、TIME_WAIT状态详解
TIME_WAIT=2MSL的原因
MSL(Maximum Segement Lifetime):报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。
2MSL的时间是从客户端接收到FIN后发送ACK开始计时的。如果在TIME-WAIT 时间内,因为客户端的ACK没有传输到服务端,客户端又接收到了服务端重发的FIN报文,那么2MSL时间将重新计时。
在Linux系统里2MSL默认是60秒,那么一个MSL也就是 30 秒。Linux系统停留在TIME_WAIT的时间为固定的60秒。(如果要修改TIME_WAIT的时间长度,只能修改Linux内核代码里TCP_TIMEWAIT_LEN 的值,并重新编译 Linux内核。)
TIME_WAIT的作用
- 防止旧连接的数据包
经过2MSL的时间足以让两个方向的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。 - 保证连接正确的关闭
等待足够的时间以确保最后的ACK能让被动关闭方接收,从而帮其正常关闭。
TIME_WAIT没有等待时间或者时间太短导致的问题
- 客户端四次挥手的最后⼀个ACK报文如果在网络中被丢失了,此时如果客户端TIME_WAIT过短或没有,则就直接进入了CLOSED状态了,那么服务端则会⼀直处在LASE_ACK状态。
- 当客户端发起建立连接的 SYN 请求报文后,服务端会发送 RST报文给客户端,连接建立的过程就会被终止。
如果等待的时间足够长的话
- 服务端正常收到四次挥手的最后⼀个ACK报文,则服务端正常关闭连接。
- 服务端没有收到四次挥⼿的最后⼀个ACK报文时,则会重发FIN关闭连接报文并等待新的ACK报文。
过多的TIME_WAIT导致的问题
- 客户端端口资源限制
- 客户端TIME_WAIT过多,就会导致端口资源被占用,因为端口就65536个,被占满就会导致无法创建新的连接。
- 服务端受系统资源限制
- 由于⼀个四元组表示TCP连接,理论上服务端可以建立很多连接,服务端确实只监听⼀个端口,但是会把连接扔给处理线程,所以理论上监听的端口可以继续监听。
- 但是线程池处理不了那么多⼀直不断的连接了。所以当服务端出现大量TIME_WAIT时,系统资源被占满时,会导致处理不过来新的连接。
优化TIME_WAIT
- 连接复用
打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项,
可以复用处于TIME_WAIT的socket 为新的连接所用。 有⼀点需要注意的是,tcp_tw_reuse功能只能用于客户端(连接发起方),因为开启了该功能,在调用connect() 函数时,内核会随机找⼀个time_wait 状态超过1秒的连接给新的连接复用。 - 连接重置
net.ipv4.ip_local_port_range net.ipv4.tcp_max_tw_buckets,默认值是18000,当系统中处于TIME_WAIT的连接一旦超过这个值时,系统就会将后面的TIME_WAIT连接状态重置。 这个方法过于暴力,而且治标不治本,带来的问题远比解决的问题多,不推荐使用。 - 直接关闭
使用SO_LINGER,应用强制使用RST关闭,如果 l_onoff 为非0, 且 l_linger 值为 0,那么调用close 后,会立刻发送⼀个RST标志给对端,该TCP连接 将跳过四次挥手,也就跳过了TIME_WAIT状态,直接关闭。 但这为跨越 TIME_WAIT状态提供了⼀个可能,不过是⼀个非常危险的行为,不值得提倡。
四、已经建立连接,客户端出现故障
TCP保活机制
TCP 有⼀个机制是保活机制。这个机制的原理是这样的
- net.ipv4.tcp_tw_reuse = 1
- net.ipv4.tcp_timestamps=1(默认即为 1)
- so_linger.l_onoff = 1
- so_linger.l_linger = 0
- setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger,sizeof(so_linger))
定义⼀个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔⼀个时间间隔,发送⼀个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。
在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值
- net.ipv4.tcp_keepalive_time=7200 :表示保活时间是 7200 秒(2小时),也就2小时内如果没有任何连接相关的活动,则会启动保活机制。
- net.ipv4.tcp_keepalive_intvl=75:表示每次检测间隔 75 秒。
- net.ipv4.tcp_keepalive_probes=9:表示检测9次无响应,认为对方是不可达的,从而中断本次的连接。
在 Linux 系统中,最少需要经过 2小时11分15秒才可以发现⼀个死亡连接。
保活机制需考虑的问题
- 对端程序是正常正作的。当TCP保活的探测报文发送给对端, 对端会正常响应,这样TCP保活时间会被重置,等待下⼀个TCP保活时间的到来。
- 对端程序崩溃并重启。当TCP保活的探测报文发送给对端后,对端是可以响应的,但由于没有该连接的有效信息,会产生⼀个RST报文,这样很快就会发现TCP连接已经被重置。
- 是对端程序崩溃,或对端由于其他原因导致报文不可达。当TCP保活的探测报⽂发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP会报告该TCP连接已经死亡。