关闭连接的⽅式通常有两种,分别是 RST 报⽂关闭和 FIN 报⽂关闭。
如果进程异常退出了,内核就会发送 RST 报⽂来关闭,它可以不⾛四次挥⼿流程,是⼀个暴⼒关闭连接的⽅式。
安全关闭连接的⽅式必须通过四次挥⼿,它由进程调⽤ close 和 shutdown 函数发起 FIN 报⽂( shutdown 参数须传⼊ SHUT_WR 或者 SHUT_RDWR 才会发送 FIN )。
1 、 close 和 shutdown 有什么区别
调⽤了 close 函数意味着完全断开连接,完全断开不仅指⽆法接收数据,⽽且也不能发送数据。
此时,调⽤了 close 函数的⼀⽅的连接叫做「孤⼉连接」,如果你⽤ netstat -p 命令,会发现连接对应的进程名为空。
使⽤ close 函数关闭连接是不优雅的。于是,就出现了⼀种优雅关闭连接的 shutdown 函数,它可以控制只关闭 ⼀个⽅向的连接
其中第⼆个参数决定断开连接的⽅式,主要有三种:
( 1 ) SHUT_RD(0)
关闭连接的「读」这个⽅向 ,如果接收缓冲区有已接收的数据,则将会被丢弃,并且后续再收到新的数据,会对数据进⾏ ACK ,然后悄悄地丢弃。也就是说,对端还是会接收到 ACK ,在这种情况下根本不知道数据已经被丢弃了。
( 2 ) SHUT_WR(1)
关闭连接的「写」这个⽅向 ,这就是常被称为「半关闭」的连接。如果发送缓冲区还有未发送的数据,将被⽴即发送出去,并发送⼀个 FIN 报⽂给对端。
( 3 ) SHUT_RDWR(2)
相当于 SHUT_RD 和 SHUT_WR 操作各⼀次,关闭套接字的读和写两个⽅向。
2 、 FIN_WAIT1 状态的优化
如果 FIN_WAIT1 状态连接很多,我们就需要考虑降低 tcp_orphan_retries 的值。当重传次数超过
tcp_orphan_retries 时,连接就会直接关闭掉(即:新增的孤⼉连接将不再⾛四次挥⼿,⽽是直接发送 RST 复位报⽂强制关闭)。
注: tcp_max_orphans 参数,定义了「孤⼉连接」的最⼤数量。
3 、 FIN_WAIT2 状态的优化
当主动⽅收到 ACK 报⽂后,会处于 FIN_WAIT2 状态,就表示主动⽅的发送通道已经关闭,接下来将等待对⽅发送FIN 报⽂,关闭对⽅的发送通道。
这时,如果连接是⽤ shutdown 函数关闭的,主动⽅连接可以⼀直处于 FIN_WAIT2 状态,因为它可能还可以发送 或接收数据。
但对于 close 函数关闭的孤⼉连接,由于⽆法再发送和接收数据,所以这个状态不可以持续太久,⽽tcp_fin_timeout 控制了这个状态下连接的持续时⻓,默认值是 60 秒(与 TIME_WAIT 状态持续的时间是相同的)。 它意味着对于孤⼉连接(调⽤ close 关闭的连接),如果在 60 秒后还没有收到 FIN 报⽂,连接就会直接关 闭。
4 、 TIME_WAIT 状态的优化
TIME_WAIT 的状态尤其重要,主要是两个原因
( 1 )防⽌收到历史数据,从⽽导致数据错乱的问题
若 TIME_WAIT 等待时间过短,被延迟的数据包抵达后会发⽣什么呢?
TIME_WAIT 设计为 2MSL ,⾜以让两个⽅向上的数据包都被丢弃,使得原来连接的数据包在⽹络中都⾃然消失,再出现的数据包⼀定都是新建⽴连接所产⽣的。
注:
MSL 全称是 Maximum Segment Lifetime ,它定义了⼀个报⽂在⽹络中的最⻓⽣存时间(报⽂每经过⼀次路由器的转发,IP 头部的 TTL 字段就会减 1 ,减到 0 时报⽂ 就被丢弃,这就限制了报⽂的最⻓存活时间)。
( 2 )为什么是 2 MSL 的时⻓呢?
这其实是相当于⾄少允许报⽂丢失⼀次。⽐如,若 ACK 在⼀个 MSL 内丢失,这样被 动⽅ ᯿ 发的 FIN 会在第 2 个 MSL 内到达, TIME_WAIT 状态的连接可以应对。
在 Linux 系统中, MSL 的值固定为 30 秒。
等待⾜够的时间以确保最后的 ACK 能让被动关闭⽅接收,从⽽帮助其正确关闭
( 3 )假设 TIME_WAIT 没有等待或等待的时间过短,断开连接会造成什么?
如上图红⾊框框客户端四次挥⼿的最后⼀个 ACK 报⽂如果在⽹络中被丢失了,此时如果客户端 TIMEWAIT 过短或 没有,则就直接进⼊了 CLOSE 状态了,那么服务端则会⼀直处在 LAST-ACK 状态。
当客户端发起建⽴连接的 SYN 请求报⽂后,服务端会发送 RST 报⽂给客户端,连接建⽴的过程就会被终⽌。
注:
Linux 提供了 tcp_max_tw_buckets 参数,当 TIME_WAIT 的连接数量超过该参数时,新关闭的连接就不再经历TIME_WAIT ⽽直接关闭。查看系统的 TIME_WAIT 的连接数量: