【计算机网络】TCP4次挥手中的TIME_WAIT状态(面试题小结)

1、为什么 TIME_WAIT 等待的时间是 2MSL?

MSL 是 Maximum Segment Lifetime,报⽂最⼤⽣存时间, 它是任何报⽂在⽹络上存在的最⻓时间,超过这个时间报⽂将被丢弃。因为 TCP 报⽂基于是 IP 协议的,⽽ IP 头中有⼀个 TTL 字段,是 IP 数据报可以经过的最⼤路由数,每经过⼀个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报⽂通知源主机。

MSL 与 TTL 的区别: MSL 的单位是时间,⽽ TTL 是经过路由跳数。所以 MSL 应该要⼤于等于 TTL 消耗为 0 的时间,以确保报⽂已被⾃然消亡。

TIME_WAIT 等于2 倍的 MSL,比较合理的解释是: 网络中可能存在来⾃发送⽅的数据包,当这些发送⽅的数据包被接收⽅处理后又会向对方发送响应,所以⼀来⼀回需要等待 2 倍的时间。

⽐如如果被动关闭⽅没有收到断开连接的最后的 ACK 报⽂,就会触发超时重发 Fin 报⽂,另⼀⽅接收到 FIN 后,会重发 ACK 给被动关闭⽅, ⼀来⼀去正好 2 个 MSL。

2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK没有传输到服务端,客户端⼜接收到了服务端重发的 FIN 报⽂,那么 2MSL 时间将重新计时。

在 Linux 系统⾥ 2MSL 默认是 60 秒,那么⼀个 MSL 也就是 30 秒。Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。

其定义在 Linux 内核代码⾥的名称为TCP_TIMEWAIT_LEN:

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT
 state, about 60 seconds */

如果要修改 TIME_WAIT 的时间⻓度,只能修改 Linux 内核代码⾥ TCP_TIMEWAIT_LEN 的值,并᯿新编译 Linux内核。

2、为什么需要 TIME_WAIT 状态?

主动发起关闭连接的⼀⽅,才会有 TIME-WAIT 状态。

需要 TIME-WAIT 状态,主要是两个原因:

  • 防⽌具有相同「四元组」的「旧」数据包被收到;
  • 保证「被动关闭连接」的⼀⽅能被正确的关闭,即保证最后的 ACK 能让被动关闭⽅接收,从⽽帮助其正常关闭;

(1)原因⼀:防止旧连接的数据包

假设 TIME-WAIT 没有等待时间或时间过短,被延迟的数据包抵达后会发⽣什么呢?

【计算机网络】TCP4次挥手中的TIME_WAIT状态(面试题小结)

  • 如上图⻩⾊框框服务端在关闭连接之前发送的 SEQ = 301 报⽂,被⽹络延迟了。
  • 这时有相同端⼝的 TCP 连接被复⽤后,被延迟的 SEQ = 301 抵达了客户端,那么客户端是有可能正常接收这个过期的报⽂,这就会产⽣数据错乱等严重的问题。

所以,TCP 就设计出了这么⼀个机制,经过 2MSL 这个时间,⾜以让两个⽅向上的数据包都被丢弃,使得原来连接的数据包在⽹络中都⾃然消失,再出现的数据包⼀定都是新建⽴连接所产⽣的。

(2)原因⼆:保证连接正确关闭

TIME-WAIT 另一个作⽤是等待⾜够的时间以确保最后的 ACK 能让被动关闭⽅接收,从⽽帮助其正常关闭。

假设 TIME-WAIT 没有等待时间或时间过短,断开连接会造成什么问题呢?

【计算机网络】TCP4次挥手中的TIME_WAIT状态(面试题小结)

  • 如上图红⾊框框客户端四次挥⼿的最后⼀个 ACK 报⽂如果在⽹络中被丢失了,此时如果客户端 TIME-WAIT 过短或没有,则就直接进⼊了 CLOSED 状态了,那么服务端则会⼀直处在 LASE_ACK 状态。
  • 当客户端发起建⽴连接的 SYN 请求报⽂后,服务端会发送 RST 报⽂给客户端,连接建⽴的过程就会被终⽌。

如果 TIME-WAIT 等待足够长的情况就会遇到两种情况:

  • 服务端正常收到四次挥⼿的最后⼀个 ACK 报⽂,则服务端正常关闭连接。
  • 服务端没有收到四次挥⼿的最后⼀个 ACK 报⽂时,则会᯿发 FIN 关闭连接报⽂并等待新的 ACK 报⽂。

所以客户端在 TIME-WAIT 状态等待 2MSL 时间后,就可以保证双⽅的连接都可以正常的关闭。

3、TIME_WAIT 过多有什么危害?

如果服务器有处于 TIME-WAIT 状态的 TCP,则说明是由服务器⽅主动发起的断开请求。

过多的 TIME-WAIT 状态主要的危害有两种:

  • 第⼀是内存资源占用;
  • 第⼆是对端⼝资源的占⽤,⼀个 TCP 连接⾄少消耗⼀个本地端口;

第⼆个危害是会造成严᯿的后果的,要知道,端⼝资源也是有限的,⼀般可以开启的端⼝为 32768~61000 ,也可以通过如下参数设置指定。

net.ipv4.ip_local_port_range

如果发起连接⼀⽅的 TIME_WAIT 状态过多,占满了所有端⼝资源,则会导致⽆法创建新连接。

客户端受端⼝资源限制:

  • 客户端TIME_WAIT过多,就会导致端⼝资源被占⽤,因为端⼝就65536个,被占满就会导致⽆法创建新的连接。

服务端受系统资源限制:

  • 由于⼀个四元组表示 TCP 连接,理论上服务端可以建⽴很多连接,服务端确实只监听⼀个端⼝ 但是会把连接扔给处理线程,所以理论上监听的端⼝可以继续监听。但是线程池处理不了那么多⼀直不断的连接了。所以当服务端出现⼤量 TIME_WAIT 时,系统资源被占满时,会导致处理不过来新的连接。

4、如何优化 TIME_WAIT?

各种方法都是有利有弊的。主要方法如下:

  • 打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项;
  • 输入net.ipv4.ip_local_port_rangenet.ipv4.tcp_max_tw_buckets
  • 程序中使⽤ SO_LINGER ,应⽤强制使⽤ RST 关闭。

(1)方式⼀:net.ipv4.tcp_tw_reuse 和 tcp_timestamps

如下的 Linux 内核参数开启后,则可以复⽤处于 TIME_WAIT 的 socket 为新的连接所⽤。

有⼀点需要注意的是,tcp_tw_reuse 功能只能⽤客户端(连接发起⽅),因为开启了该功能,在调⽤ connect()函数时,内核会随机找⼀个 time_wait 状态超过 1 秒的连接给新的连接复⽤。

net.ipv4.tcp_tw_reuse = 1

使⽤这个选项,还有⼀个前提,需要打开对 TCP 时间戳的⽀持,即

net.ipv4.tcp_timestamps=1(默认即为 1)

这个时间戳的字段是在 TCP 头部的「选项」⾥,⽤于记录 TCP 发送⽅的当前时间戳和从对端接收到的最新时间戳。

由于引⼊了时间戳,我们在前⾯提到的 2MSL 问题就不复存在了,因为᯿复的数据包会因为时间戳过期被⾃然丢弃。

(2)方式⼆:net.ipv4.tcp_max_tw_buckets

这个值默认为 18000,当系统中处于 TIME_WAIT 的连接⼀旦超过这个值时,系统就会将后⾯的 TIME_WAIT 连接状态重置。

这个⽅法过于暴⼒,而且治标不治本,带来的问题远⽐解决的问题多,不推荐使⽤。

(3)方式三:程序中使⽤ SO_LINGER

可以通过设置 socket 选项,来设置调⽤ close 关闭连接⾏为。

struct linger so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger,sizeof(so_linger));

如果 l_onoff 为⾮ 0, 且 l_linger 值为 0,那么调⽤ close 后,会⽴该发送⼀个 RST 标志给对端,该 TCP 连接将跳过四次挥⼿,也就跳过了 TIME_WAIT 状态,直接关闭。

但这为跨越 TIME_WAIT 状态提供了⼀个可能,不过是⼀个⾮常危险的⾏为,不值得提倡。

整理自小林coding所著的《图解网络》,仅做学习用,侵删

上一篇:javascript-TinyMCE将HREF从“ / category / product-name”更改为“ ../../../../category/product-name”


下一篇:Javascript-使用TinyMCE RTF编辑器-如何修改文本框大小?