前些天,有朋友问我关于 FIN_WAIT2 的问题:如果主动关闭的一方在进入 FIN_WAIT2 状态后没有收到被动关闭的一方发送的 FIN 包,那么会怎样?
让我们热热身,通过一张旧图来回忆一下 TCP 关闭连接时的情况:
TCP Close
按照正常的状态迁移路径,当 FIN_WAIT2 收到 FIN 包后会迁移到 TIME_WAIT 状态。如果没有收到 FIN 包,那么连接状态会如何迁移,我们不妨测试一下:
代码语言:javascript复制#!/usr/bin/env python
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 1234))
s.listen(1)
c, _ = s.accept()
time.sleep(1000)
c.close()
如上是用 Python 实现的一个简单的 server 演示代码,需要注意的是我在 close 前设置了一个巨大的延迟时间,从而达到拖延服务端发出 FIN 包的目的。与之相对应的我们再实现一个简单的 client 演示代码,它没什么可说的,就是连上后直接关闭:
代码语言:javascript复制#!/usr/bin/env python
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 1234))
s.close()
测试的时候先启动服务端监听 1234 端口,再启动客户端连接 1234 端口,为了确认整个过程是否符合我们的预期,可以使用 tcpdump 监听通讯过程:
tcpdump -t -nn -i any port 1234
如图可见:在三次握手之后,客户端关闭了连接,服务端确认后没有发出 FIN 包,所以整个过程符合我们的预期,同时为了判断 FIN_WAIT2 存在了多久,写了如下代码:
代码语言:javascript复制shell> while sleep 1; do
netstat -ant | grep FIN_WAIT2 | while read content; do
echo -n $(date "%T") ""
echo $content
done
done
监控发现,在本例中 FIN_WAIT2 存在的时间大约是一分钟左右:
FIN_WAIT2 存在的时间
实际上此时间是「net.ipv4.tcp_fin_timeout」控制的,不过在测试中发现,FIN_WAIT2 存在的时间并不是精确的等于 tcp_fin_timeout 的设置,存在一定的偏差。此外,需要说明的是在 tcp_fin_timeout 后,FIN_WAIT2 并没有迁移到 TIME_WAIT,而是直接关闭了。
关于 tcp_fin_timeout 的介绍,可以参考内核的说明:
The length of time an orphaned (no longer referenced by any application) connection will remain in the FIN_WAIT_2 state before it is aborted at the local end. While a perfectly valid “receive only” state for an un-orphaned connection, an orphaned connection in FIN_WAIT_2 state could otherwise wait forever for the remote to close its end of the connection. Cf. tcp_max_orphans Default: 60 seconds
介绍中提到了 orphan 的概念,指的是当一个 socket 不再被任何应用引用时,它便成为了一个孤儿,而 tcp_fin_timeout 限定了成为孤儿的 FIN_WAIT2 所能存活的时间。
注:orphan 太多的话会:The “Out of socket memory” error。
或许有人会问:如果 FIN_WAIT2 没有成为孤儿,那么会如何:
代码语言:javascript复制#!/usr/bin/env python
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 1234))
s.shutdown(socket.SHUT_WR)
time.sleep(1000)
s.close()
在这一版的 client 代码中,我们没有直接 close 连接,而是通过 shutdown 来关闭,此时客户端同样会发送 FIN 包,但是并没有释放连接,所以本例中的 FIN_WAIT2 和上例中的 FIN_WAIT2 不同,其并不会成为孤儿。通过测试发现,此时 tcp_fin_timeout 将失效,而 本例中的 FIN_WAIT2 会一直存在,直到客户端 close 连接或者退出。
实际上,同其它 TCP 状态相比,通常 FIN_WAIT2 并不会给你添什么麻烦。如果你统计服务器上的 TCP 状态,你会发现它们多数时候都很少,如果不是,那么一定是应用层面上出了问题。至于 tcp_fin_timeout,我并不建议大家把它设置得太小,因为如上所说,正常情况下,TCP 连接并不会在 FIN_WAIT2 状态上停留太久,假设真的出现 FIN 包丢失之类的情况,那么给 FIN_WAIT2 状态一个合理的存在周期是必要的,毕竟丢失的 FIN 包可能会重传,在这一点上和 TIME_WAIT 为什么要存在 2MSL 是同样的原因。既然 TIME_WAIT 缺省存在 60 秒,那么 tcp_fin_timeout 缺省设置为 60 同样是一个合理的选择。