0%

close_wait

1. CLOSE_WAIT是什么

TCP关闭时

  • 主动关闭: 发FIN(FIN_WAIT_1) –> 收ACK(FIN_WAIT_2) –> 收FIN(TIME_WAIT) –> 发ACK, TIME_WAIT会持续 2*MSL(1-4分钟)
  • 被动关闭: 收FIN(CLOSE_WAIT) –> 发ACK –> 发FIN(LAST_ACK) –> 收ACK(CLOSED), 如果程序不主动调用 close(fd) 关闭套接字,就不会主动发送FIN,就会一直在 CLOSE_WAIT 状态

所以 CLOSE_WAIT 存在于被动关闭连接的情况,一般是服务器被动关闭,所以一般需要服务器去解决。

2. 产生的原因

主要有两点:

  1. 代码中没有写关闭连接的代码,存在bug;
  2. 该连接的业务代码处理事件太长,代码还在处理,对方已经发起断开连接的请求了;这时候会存在一段时间的 CLOS_WAIT,直到服务端处理到这里。

总的来说,就是服务端没有及时调用close()关闭套接字。

3. 一直不关闭,最多会存在多长时间

内核会定时探测TCP连接是否存在,如果不存在,会自动关闭该TCP,回收系统资源。内核依靠如下参数来探测:

  • tcp_keepalive_time(7200): 如果在该参数指定的秒数内,TCP连接一直处于空闲,内核就开始向对端发起探测,看对端是否还在;
  • tcp_keepalive_intvl(75): 发起探测时,每隔这么多秒数,探测一次;
  • tcp_keepalive_probes(9): 总共探测这么多次;

所以 CLOSE_WAIT 状态最多维持 tcp_keepalive_time + tcp_keepalive_intvl * tcp_keepalive_probes = 7200 +75*9 = 7875s,约130分钟,2小时多一点。

这只是内核的限制,还可以在程序里使用 setsocketopt(fd, SOL_TCP, TCP_KEEPIDLE...) 对每个TCP连接设置。

4. 有什么影响

CLOSE_WAIT状态的端口表示正在被占用,如果没有设置端口复用,这个端口就变得不可用,过多的CLOSE_WAIT会耗尽系统的可用端口,新的连接进不来,服务变得不可用。

5. 如何解决

  1. 程序方面,需要及时处理连接被动关闭的状态,特别是 recv() 收到0字节,表示对端关闭了;
  2. 系统方面,可以将 keepalive 相关的参数设置的小一些,让系统尽快探测到TCP不可用,尽快回收资源。

6. 主动关闭时的两个较长的等待时间

  • FIN_WAIT_2: 等待对端发送 FIN
  • TIME_WAIT: 等待2MSL

调用了shutdown(),但是没有调用close(),就会等在FIN_WAIT_2状态。跟这个状态有关的内核参数是:

  • net.ipv4.tcp_fin_timeout(30): 注意,这个参数只对调用了close()的孤儿tcp有效,如果程序只是调用了shutdown(),但是没有close(),该参数并不起作用。

一般主动关闭连接的一方较常见 TIME_WAIT,产生的原因是主动关闭的时候,要等着对端收到了最后一个ACK才回收相应资源,防止对端没有收到ACK又重新发送最后一个FIN。但这也是占用了一个端口,跟这个状态有关的参数是:

  • net.ipv4.tcp_max_tw_buckets(180000): TIME_WAIT的最大数量,超过这个会被立刻清理,并打印警告信息
  • net.ipv4.tcp_tw_timeout(60): TIME_WAIT的过期时间
  • net.ipv4.tcp_tw_reuse(0): TIME_WAIT状态重用
  • net.ipv4.tcp_recycle(0): 开启 TIME_WAT 快速回收。不再是2MSL,而是一个RTO(动态计算的数据包重传超时时间)。还需要tcp_timestamps保证旧连接的数据包不会被新连接接收。NAT环境可能导致drop掉SYN包,无法发起新的连接;
  • net.ipv4.tcp_timestamps(1): 检查请求数据包的时间戳,快速回收需要配合这个时间戳选项;

7. net.ipv4.tcp_timestamps=1和net.ipv4.tcp_recycle=1在NAT环境下的影响

简单来说就是NAT会修改目的IP,但有的不修改tcp头里的timestamp,导致不同的客户端通过NAT过来的数据包时间戳不一样,时间戳小的就接不进server,所以server一般不开启 net.ipv4.tcp_recycle。