TIME_WAIT与SO_LINGER
TIME_WAIT
主动关闭方在收到被动关闭方的FIN包后并返回ACK后,会进入TIME_WAIT状态,TIME_WAIT状态又称2MSL状态,每个TCP连接都必须有一个最大报文段生存时间MSL,在网络传输中超过这个时间的报文段将被丢弃。当TCP连接发起一个主动关闭,并发出最后一个ACK时,必须在TIME_WAIT状态停留两倍MSL时间,在2MSL等待期间,定义这个连接的插口(客户端IP地址和端口号,服务器IP地址和端口号的四元组)将不能再被使用。
主动发起关闭连接的一方,才会有 TIME-WAIT 状态。 需要TIME-WAIT状态,主要是两个原因:
-
防止历史连接中的数据,被后面相同四元组的连接错误的接收;
-
保证「被动关闭连接」的一方,能被正确的关闭;
序列号,是 TCP 一个头部字段,标识了 TCP 发送端到 TCP 接收端的数据流的一个字节,因为 TCP 是面向字节流的可靠协议,为了保证消息的顺序性和可靠性,TCP 为每个传输方向上的每个字节都赋予了一个编号,以便于传输成功后确认、丢失后重传以及在接收端保证不会乱序。序列号是一个 32 位的无符号数,因此在到达 4G 之后再循环回到 0。
初始序列号,在 TCP 建立连接的时候,客户端和服务端都会各自生成一个初始序列号,它是基于时钟生成的一个随机数,来保证每个连接都拥有不同的初始序列号。初始化序列号可被视为一个 32 位的计数器,该计数器的数值每 4 微秒加 1,循环一次需要 4.55 小时。
序列号和初始化序列号并不是无限递增的,会发生回绕为初始值的情况,这意味着无法根据序列号来判断新老数据。
tcp协议头中有seq和ack_seq两个字段,分别代表序列号和确认号。tcp协议通过序列号标识发送的报文段。seq的类型是__u32,当超过__u32的最大值时,会回绕到0。
一个tcp流的初始序列号(ISN)并不是从0开始的,而是采用一定的随机算法产生的,因此ISN可能很大(比如(2^32-10)),因此同一个tcp流的seq号可能会回绕到0。而我们tcp对于丢包和乱序等问题的判断都是依赖于序列号大小比较的。此时就出现了所谓的tcp序列号回绕(sequence wraparound)问题。
-
TCP 设计了 TIME_WAIT 状态,状态会持续 2MSL 时长,这个时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
-
是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。
TIME-WAIT状态过多危害
- 第一是占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等;
- 第二是占用端口资源,端口资源也是有限的,一般可以开启的端口为 32768~61000,也可以通过 net.ipv4.ip_local_port_range参数指定范围。
出现过多的原因
-
HTTP 没有使用长连接
-
HTTP 长连接超时
-
HTTP 长连接的请求数量达到上限
SO_LINGER
SO_LINGER选项的作用是等待发送缓冲区中的数据发送完成,但是并不保证发送缓冲区中的数据一定被对端接收(对端宕机或线路问题),只是说会等待一段时间让这个过程完成。
struct linger {
int l_onoff; //0=off, nonzero=on(开关)
int l_linger; //linger time(延迟时间)
}
l_onoff | l_linger | closesocket | 发送队列 | 底层行为 |
---|---|---|---|---|
0 | 关闭 | 立即返回 | 保持直到发送完成 | 系统接管套接字并保证将数据发送至对端。(就是正常的close) |
1 | 0 | 立即返回 | 立即放弃 | 直接发送RST包,自身立即复位,不用经过2MSL状态。对端收到复位错误号。 |
1 | 1 | 阻塞直到l_linger时间超时或数据发送完成。 | 在超时时间段内保持尝试发送,若超时则立即放弃。 | 设置超时时间,若超时未完成数据发送,则立即返回按linger = 0的行为关闭 |
Re:
https://zhuanlan.zhihu.com/p/99943313
https://blog.csdn.net/xiaokaige198747/article/details/75389113
https://blog.csdn.net/songchuwang1868/article/details/90369445
https://xiaolincoding.com/network/3_tcp/tcp_interview.html#为什么需要-time-wait-状态