TCP TIME_WAIT 状态 及相关问题优化

发布时间 2023-07-01 15:11:24作者: 白露~

TCP 是一种面向连接的可靠的传输协议,它在建立和释放连接时,需要经过一系列的握手和挥手过程。在这个过程中,会涉及到一些不同的状态,其中一个比较常见但又容易被误解的状态就是 TIME_WAIT 状态。本文将从以下几个方面介绍 TIME_WAIT 状态的原理和优化方法:

  • TIME_WAIT 状态是如何产生的?
  • TIME_WAIT 状态有什么作用和意义?
  • TIME_WAIT 状态会带来什么问题和影响?
  • 如何避免或减少 TIME_WAIT 状态的产生?

TIME_WAIT 状态是如何产生的?

要了解 TIME_WAIT 状态是如何产生的,我们首先要看一下 TCP 的状态转换图,如下所示:

 

这幅图来自 RFC 793 ,是 TCP 协议的标准文档。从这幅图中,我们可以看到,TIME_WAIT 状态主要出现在以下两种情况:

  • 当一方主动关闭连接(active close)时,它会先发送一个 FIN 包表示不再发送数据,然后等待对方的 ACK 包和 FIN 包,收到后再回复一个 ACK 包,最后进入 TIME_WAIT 状态。这种情况下,TIME_WAIT 状态持续 2 倍的 MSL(Maximum Segment Lifetime,最大报文段生存时间),然后关闭连接。MSL 是指一个 TCP 报文在网络中存在的最长时间,超过这个时间,TCP 报文就一定不会被发送到接收方。MSL 的值根据不同的网络环境而定,一般为 30 秒或 60 秒。
  • 当双方同时关闭连接(simultaneous close)时,它们都会发送一个 FIN 包表示不再发送数据,并回复对方的 FIN 包和 ACK 包,然后进入 CLOSING 状态。在 CLOSING 状态下,如果收到对方的 ACK 包,则进入 TIME_WAIT 状态。这种情况下,TIME_WAIT 状态也持续 2 倍的 MSL,然后关闭连接。

TIME_WAIT 状态有什么作用和意义?

TIME_WAIT 状态存在的目的主要有两个:

  • 防止旧连接的数据包干扰新连接。由于 TCP 连接是由四元组(源 IP 地址、源端口、目标 IP 地址、目标端口)唯一标识的,如果在一个旧连接关闭后很快建立一个新连接,并且使用相同的四元组,那么可能会出现旧连接中延迟或重传的数据包被新连接误认为是自己的数据包的情况。这样就会导致数据错乱或错误。为了防止这种情况发生,TCP 协议规定,在一个旧连接关闭后,在 2 倍 MSL 的时间内不能建立相同四元组的新连接。这样可以保证旧连接中所有可能存在的数据包都在网络中消失了。
  • 防止最后一个 ACK 包丢失导致对方重传 FIN 包。由于 TCP 协议采用了可靠传输机制,如果一个数据包没有收到对方的确认(ACK)包,那么就会认为该数据包丢失,并进行重传。在关闭连接的过程中,如果主动关闭方发送的最后一个 ACK 包丢失了,那么被动关闭方就会认为自己发送的 FIN 包丢失了,并进行重传。如果主动关闭方没有维持 TIME_WAIT 状态,那么它就无法正确地回复 ACK 包,而只能发送 RST 包表示连接已经关闭。这样被动关闭方就会认为发生了错误,而无法正常地关闭连接。为了防止这种情况发生,TCP 协议规定,在主动关闭方发送最后一个 ACK 包后,在 2 倍 MSL 的时间内维持 TIME_WAIT 状态,以便在收到对方重传的 FIN 包时能够重新发送 ACK 包。

TIME_WAIT 状态会带来什么问题和影响?

虽然 TIME_WAIT 状态有它的作用和意义,但是在一些场景下,它也会带来一些问题和影响,主要有以下几个方面:

  • 占用端口资源。由于处于 TIME_WAIT 状态的连接不能立即释放端口,而是要等待一段时间,这就会导致端口资源的浪费。如果在短时间内建立了大量的短连接,那么就可能出现端口耗尽的情况,从而无法建立新的连接。这对于一些需要高并发的应用程序来说,是一个很大的限制。
  • 占用内存资源。由于处于 TIME_WAIT 状态的连接还需要维护一些内核数据结构,比如 socket 和 TCP 控制块(TCB),这就会占用一些内存资源。如果有大量的 TIME_WAIT 状态的连接存在,那么就可能导致内存不足的情况,从而影响系统的性能和稳定性。
  • 增加 CPU 开销。由于处于 TIME_WAIT 状态的连接还需要处理一些网络事件,比如收发数据包、超时计时、状态转换等,这就会增加 CPU 的开销。如果有大量的 TIME_WAIT 状态的连接存在,那么就可能导致 CPU 负载过高的情况,从而影响系统的性能和稳定性。

如何避免或减少 TIME_WAIT 状态的产生?

针对 TIME_WAIT 状态带来的问题和影响,有以下几种可能的优化方法:

  • 让被动关闭连接的一方(服务器端或客户端)先调用 close() 函数,这样主动关闭连接的一方就不会进入 TIME_WAIT 状态,而是进入 FIN_WAIT_2 状态。这种方法需要修改应用程序的逻辑,可能不太实际。
  • 设置 SO_REUSEADDR 套接字选项,让内核允许在 TIME_WAIT 状态下重用端口。这种方法可以避免因为端口耗尽而无法建立新连接的问题,但是不能减少 TIME_WAIT 状态的数量。
  • 设置 net.ipv4.tcp_tw_reuse 或 net.ipv4.tcp_tw_recycle 参数,让内核允许在 TIME_WAIT 状态下重用或回收连接。这种方法可以减少 TIME_WAIT 状态的数量,但是可能会引起一些潜在的问题,比如与 NAT 设备或负载均衡器不兼容,或者导致旧连接的数据包干扰新连接。因此不推荐使用这种方法。
  • 设置 net.ipv4.tcp_fin_timeout 参数,调整 FIN_WAIT_2 状态的超时时间。这种方法可以缩短连接的释放过程,但是可能会导致数据丢失或错误。因此也不推荐使用这种方法。

总之,TIME_WAIT 状态是 TCP 协议为了保证可靠传输而设计的一种状态,它有它存在的必要性和合理性。在优化 TIME_WAIT 状态时,我们应该根据具体的场景和需求来选择合适的方法,并权衡其利弊。我们不应该盲目地去消除或减少 TIME_WAIT 状态,而应该温暖地看待它,并尊重它的作用和意义。

参考资料