TCP 四次挥手过程和状态变迁
TCP 断开连接是通过四次挥手方式。 双方都可以主动断开连接,断开连接后主机中的「资源」将被释放。
- 客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
- 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSED_WAIT 状态。
- 客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
- 等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
- 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
- 服务器收到了 ACK 应答报文后,就进入了 CLOSED 状态,至此服务端已经完成连接的关闭。
- 客户端在经过 2MSL 一段时间后,自动进入 CLOSED 状态,至此客户端也完成连接的关闭。
这里一点需要注意是:主动关闭连接的,才有 TIME_WAIT 状态。
为什么挥手需要四次?
- TCP 是全双工通信,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
- 服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等 服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。
所以要分为两次发送。这个时候就必须最少需要 2+2= 4 次挥手来完全终止这个连接。
为什么需要CLOSE_WAIT 状态?
在服务器收到客户端关闭连接的请求并告诉客户端自己已经成功收到了该请求之后,服务器进入了 CLOSE-WAIT 状态,然而此时有可能服务端还有一些数据没有传输完成,因此不能立即关闭连接,而 CLOSE-WAIT 状态就是为了保证服务器在关闭连接之前将待发送的数据发送完成。
close_wait状态太多怎么办?
1.查看是否时服务端代码bug,没有关闭连接
2.调整系统参数(tcp参数),缩短CLOSE_WAIT的持续时间
为什么需要 TIME_WAIT 状态?
首先,主动发起关闭连接的一方,才会有 TIME-WAIT 状态。
需要 TIME-WAIT 状态,主要是两个原因:
- 防止具有相同「四元组」的「旧」数据包被收到;
- 保证「被动关闭连接」的一方能被正确的关闭,即保证最后的 ACK 能让被动关闭方接收,从而帮助其正常关 闭;
原因一:保证连接正确关闭
在四次挥手关闭tcp连接的过程中,最后一次ACK是主动关闭连接的一端发起的。如果这个ACK丢失,对方在一定时间没有收到ACK后会再次发送FIN请求,因此主动关闭的一方需要维护一个time_wait的状态,来处理对方重发的FIN请求。
原因二:防止旧连接的数据包
处理延迟到达的报文,由于网络的不稳定性,TCP报文可能延迟到达。为了避免延迟到达的报文被误认为是新tcp连接的数据(端口重用),则需要在新建tcp连接前保持一个不可用的状态,等待所有延迟报文消失,一般设置2被的MSL(报文的最大生存时间),来解决该问题。
TIME_WAIT 过多有什么危害?
如果服务器有处于 TIME-WAIT 状态的 TCP,则说明是由服务器方主动发起的断开请求。
过多的 TIME-WAIT 状态主要的危害有两种:
- 第一是内存资源占用;
- 第二是对端口资源的占用,一个 TCP 连接至少消耗一个本地端口;
第二个危害是会造成严重的后果的,要知道,端口资源也是有限的,一般可以开启的端口为 32768~61000 ,也 可以通过如下参数设置指定net.ipv4.ip_local_port_range
为什么 TIME_WAIT 等待的时间是 2MSL?
MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最⻓时间,超过这个时 间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 字段,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。
关闭方没有收到断开连接的最后的 ACK报文,就会触发超时发 Fin 报文,另一方接收到 FIN 后, 会发 ACK 给被动关闭方, 一来一去正好2个MSL。
如何优化 TIME_WAIT?
- 尽可能的采用长连接的方式,减少 TCP 的连接与断开
- 重用连接,避免频繁关闭,比如使用连接池
- 参数调优,比如开启tcp_tw_reuse选项支持timewait连接的重复使用。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP 有一个机制是保活机制。这个机制的原理是这样的:
定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间 隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。
在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
- tcp_keepalive_time=7200:表示保活时间是 7200 秒(2小时),也就 2 小时内如果没有任何连接相关的活 动,则会启动保活机制
- tcp_keepalive_intvl=75:表示每次检测间隔 75 秒;
- tcp_keepalive_probes=9:表示检测 9 次无响应,认为对方是不可达的,从而中断本次的连接。
也就是说在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接。
