最近在排查一个TCP异常现象,有很多收获,加深了我对TCP的理解,在这里记录一下。

问题现象

测试环境为windows 10,详细的抓包如下。

wireshark capture

问题的流程为:

  1. 服务端IP为192.168.10.10,端口为3001;客户端IP为192.168.10.20。
  2. 服务端重启了,在图中的No.310中可以看到服务端关闭了连接。
  3. 随后客户端尝试重连,在图中的No.331可以看到服务端与客户端完成了三次握手过程,成功建立连接。
  4. 在连接成功后很短时间(1秒)内服务端就将连接断开了,断开时的错误码是10045,在软件的log中看到了这个错误流程。
  5. 后续在客户端发送心跳时,服务端通知客户端重置连接,在图中No.543可以看到。

TIME_WAIT 状态

主动关闭连接的一方在四次挥手结束时会处在 TIME_WAIT 状态,随后需要等待2MSL时间后 TIME_WAIT 状态才会变为 CLOSED 状态。
一般我们会在服务端设置 SO_REUSEADDR 参数,这样服务端在重启时可以忽略 TIME_WAIT 的限制,快速重用之前的端口。

但是在服务端使用 SO_REUSEADDR 时,虽然能够快速重用IP和端口,但是如果客户端快速重连时可能会出现一次失败,就像问题流程中的步骤4和5一样。
虽然客户端成功完成了三次握手,但是随后却被服务端认为是连接异常而关闭了。

这可能是由于服务端在快速重用IP和端口时,会在一段时间内无法区分客户端发来的包是重启之前连接的还是重启之后连接的。

TIPS: 当我们希望客户端使用固定的端口时, SO_REUSEADDR 也可以用在客户端socket上。

静默时间

在查资料时,了解到了一个新的概念“静默时间”。

如果一台处于 TIME_WAIT 状态下的连接向关联的主机崩溃,然后在MSL内重新启动,并且使用与主机崩溃之前处于 TIME_WAIT 状态的连接相同的IP地址与端口号,那么将会怎样处理呢?在上述情况下,该连接在主机崩溃之前产生的延迟报文段会被认为属于主机重启后创建的新连接。

为了防止上述情况的发生,[RFC0793]指出在崩溃或者重启后TCP协议应当在创建新的连接之前等待相当于一个MSL的时间。该段时间被称作静默时间。

然而只有极少数实现遵循了这一点。因为绝大多数的主机在崩溃之后都需要超过一个MSL的时间才能重新启动。此外,如果上层应用程序自身已采用了校验和或者加密手段,那么此类错误会很容易检测出来。

参考资料

  • 《TCP/IP详解》卷1:协议

(全文完)