TCP的TIME_WAIT状态

1. TIME_WAIT状态

主动关闭方在收到被动关闭方的FIN包后并返回ACK后,会进入TIME_WAIT状态,TIME_WAIT状态又称2MSL状态,每个TCP连接都必须有一个最大报文段生存时间MSL,在网络传输中超过这个时间的报文段将被丢弃。当TCP连接发起一个主动关闭,并发出最后一个ACK时,必须在TIME_WAIT状态停留两倍MSL时间,在2MSL等待期间,定义这个连接的插口(客户端IP地址和端口号,服务器IP地址和端口号的四元组)将不能再被使用。2MSL状态存在有两个理由:

  • 1.允许老的重复报文分组在网络中消逝。
  • 2.保证TCP全双工连接的正确关闭。

第一个理由是假如我们在192.168.1.1:500039.106.170.184:6000建立一个TCP连接,一段时间后我们关闭这个连接,再基于相同插口建立一个新的TCP连接,这个新的连接称为前一个连接的化身。老的报文很有可能由于某些原因迟到了,那么新的TCP连接很有可能会将这个迟到的报文认为是新的连接的报文,而导致数据错乱。为了防止这种情况的发生TCP连接必须让TIME_WAIT状态持续2MSL,在此期间将不能基于这个插口建立新的化身,让它有足够的时间使迟到的报文段被丢弃。

第二个理由是因为如果主动关闭方最终的ACK丢失,那么服务器将会重新发送那个FIN,以允许主动关闭方重新发送那个ACK。要是主动关闭方不维护2MSL状态,那么主动关闭将会不得不响应一个RST报文段,而服务器将会把它解释为一个错误,导致TCP连接没有办法完成全双工的关闭,而进入半关闭状态。

TCP的TIME_WAIT状态_第1张图片

1.1 为什么是维持2MSL

  • 一个MSL是确保主动关闭方最后的ACK能够到达对端。
  • 一个MSL是确保被动关闭方重发的FIN能够被主动关闭方收到。

2.处理TIME_WAIT状态

一个web服务器最大的端口数量是65535个,如果这个服务器作为客户端不停的和服务端不停的创建短连接,就会导致有大量的TCP进入TIME_WAIT状态。

当服务端是主动关闭方,因为有TIME_WAIT状态的存在,在重启程序的时候可能会出现java.net.BindException: Address already in use的错误,这是因为这个端口TIME_WAIT状态需要等待2MSL。在RFC793中规定MSL的时间为2min,在实际使用中一般是30s或者1min,在高并发的情况下毫无疑问,这将造成大量连接无法建立的问题,那么有什么方法可以处理这些问题那?

2.1 SO_REUSEADDR

  • SO_REUSEADDR设置为1在TIME_WAIT时允许套接字端口复用。
  • SO_REUSEADDR设置为0TIME_WAIT时不允许允许套接字端口复用。

在Java中可以通过以下代码设置:

ServerSocket serverSocket = new ServerSocket();
// setReuseAddress 必须在 bind 函数调用之前执行
serverSocket.setReuseAddress(true);
SO_REUSEADDR可以用在以下四种情况下。(摘自《Unix网络编程》卷一,即UNPv1)
  1. 当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。
  2. SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可 以测试这种情况。
  3. SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同。这和2很相似,区别请看UNPv1
  4. SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP。

所以SO_REUSEADDR并不是在所有情况下都可以使用的。

参考:TCP套接字端口复用SO_REUSEADDR

2.2 TCP头部时间戳选项(TCP Timestamps Option)

时间戳可选项主要包含4个部分:

  1. kind:类型
  2. length:长度
  3. TimeStamp value:发送方时间戳
  4. TimeStamp echo reply:回显时间戳

TCP的TIME_WAIT状态_第2张图片

时间戳可选项可以处理序号回绕,判断乱序,更加准确的计算RTT,在linux中可以通过net.ipv4.tcp_timestamps设置,一般默认是1(打开)。

发送方在发送数据时将发送数据的时间X放到发送方时间戳TSval中。

接收方在接收到数据的时候将收到的时间X原封不动的放到回显时间戳TSecr中,同时将自己发送数据的时间Z放到发送方时间戳TSval中,以此类推。

TCP的TIME_WAIT状态_第3张图片

2.3 net.ipv4.tcp_tw_reuse

在linux中tcp_tw_reuse默认为0,设置为1后表示客户端允许TIME_WAIT状态重用。重用的条件是:

  1. tcp_tw_reuse选项和tcp_timestamps也必须同时打开;
  2. 开启后收到最后一个包后超过1s。

当开启tcp_tw_reuse后,主动关闭方收到包的时间戳比存储的时间戳小,则证明收到的包是一个旧的连接的包,直接丢弃。

2.4 net.ipv4.tcp_tw_recycel

在linux中tcp_tw_recycel默认为0,设置为1后开启(tcp_timestamps必须同时打开),开启tcp_tw_recycel后服务器将会缓存每个套接字的最新时间戳。对于新的连接,如果SYN包中的时间戳小于之前缓存的套接字的时间戳,则直接丢弃,否则允许复用TIME_WAIT

这种机制在经过NAT或者负载均衡后,会发生严重的问题。因为不同请求经过NAT或者负载均衡后IP可能是一样的,就会导致TIME_WAIT无法复用。所以不推荐打开这个参数。

你可能感兴趣的:(Windows运维,tcp/ip,网络,服务器)