TIME_WAIT 是客户端(主动发起方)的状态,在发送第四次挥手后进入的一个状态。服务器也有可能出现TIME_WAIT,服务器也有可能是断开连接的主动发起方。
1. TIME_WAIT
1.1 为什么等待 2MSL?
MSL
是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。MSL 与 TTL 的区别: MSL 的单位是时间,而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。TTL 的值一般是 64,Linux 将 MSL 设置为 30 秒,意味着 Linux 认为数据报文经过 64 个路由器的时间不会超过 30 秒,如果超过了,就认为报文已经消失在网络中了。
可以看到 2MSL时长 这其实是相当于至少允许报文丢失一次。比如,若客户端第四次挥手 在一个 MSL 内丢失,这样被动方重发的 第三次挥手会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对。
在 Linux 系统里 2MSL 默认是 60 秒,那么一个 MSL 也就是 30 秒。Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。
2MSL
的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时。
1.2 为什么需要 TIME_WAIT?
那么客户端为什么进入 TIME_WAIT 而不是直接关闭?保证「被动关闭连接」的一方,能被正确的关闭。
从 RFC 793 对 TIME_WAIT
状态的定义中,我们可以发现该状态的另一个重要作用,等待足够长的时间以确定远程的 TCP 连接接收到了其发出的终止连接消息 FIN
对应的 ACK
:
TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.
1. 保证「被动关闭连接」的一方,能被正确的关闭;
因为客户端发送挥手④的时候,有可能失败。
如果客户端直接关闭,服务器重发挥手③,客户端处于关闭不响应,服务器无法释放资源。
2. 防止相同四元组再次收到
在关闭一个 TCP 连接后,马上又重新建立起一个相同的 IP 地址和端口之间的 TCP 连接,后一个连接被称为前一个连接的化身,那么有可能出现这种情况,前一个连接的迷途重复报文在前一个连接终止后出现,从而被误解成从属于新的化身。
为了避免这个情 况, TIME_WAIT 状态需要持续 2MSL,因为这样就可以保证当成功建立一个 TCP 连接的时候,来自连接先前化身的重复报文已经在网络中消逝。
1.3 TIME_WAIT 过多危害?
- 第一是占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等;
- 第二是占用端口资源,端口资源也是有限的。
如果服务端出现大量的 TIME_WAIT 状态的 TCP 连接,就是说明服务端主动断开了很多 TCP 连接。问题来了,什么场景下服务端会主动断开连接呢?
- HTTP 没有使用长连接
- HTTP 长连接超时
- HTTP 长连接的请求数量达到上限
1.4 如何优化
《UNIX网络编程》一书中却说道:TIME_WAIT 是我们的朋友,它是有助于我们的,不要试图避免这个状态,而是应该弄清楚它。
如果服务端要避免过多的 TIME_WAIT 状态的连接,就永远不要主动断开连接,让客户端去断开,由分布在各处的客户端去承受 TIME_WAIT。
打开 sysctl.conf 文件,修改以下几个参数:
1 | net.ipv4.tcp_tw_recycle = 1 #表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭 |
2. CLOSE_WAIT
CLOSE_WAIT 状态是「被动关闭方」才会有的状态,而且如果「被动关闭方」没有调用 close 函数关闭连接,那么就无法发出 FIN 报文,从而无法使得 CLOSE_WAIT 状态的连接转变为 LAST_ACK 状态。
2.1 原因
当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。
当服务端出现大量 CLOSE_WAIT 状态的连接的时候,通常都是代码的问题,这时候我们需要针对具体的代码一步一步的进行排查和定位,主要分析的方向就是服务端为什么没有调用 close。
3. 异常关闭
3.1 客户端断电
客户端出现故障指的是客户端的主机发生了宕机,或者断电的场景。也就是服务端的 TCP 连接将一直处于 ESTABLISH
状态,占用着系统资源。
为了避免这种情况,TCP 搞了个保活机制。这个机制的原理是这样的:
定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。
在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:
1 | net.ipv4.tcp_keepalive_time=7200 |
- tcp_keepalive_time=7200:表示保活时间是 7200 秒(2小时),也就 2 小时内如果没有任何连接相关的活动,则会启动保活机制
- tcp_keepalive_intvl=75:表示每次检测间隔 75 秒;
- tcp_keepalive_probes=9:表示检测 9 次无响应,认为对方是不可达的,从而中断本次的连接。
也就是说在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接。
3.2 服务端崩溃
TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程。
4. 头脑风暴
TIME_WAIT 1. 保证「被动关闭连接」的一方,能被正确的关闭;2. 防止相同四元组再次收到。
服务器TIME_WAIT过多原因,说明是服务器是主动方。确认http keepalive使用,超时,数量限制。可以调整linux参数不建议。
CLOSE_WAIT 服务端的程序没有调用 close 函数关闭连接。