什么原因会导致TCP/IP在不断开连接的情况下丢失数据包?

11

我有一个基于web的应用程序和一个客户端,都是用Java编写的。值得一提的是,客户端和服务器都在Windows上。客户端通过Apache HttpClient发出HTTP GET请求。服务器会阻塞最多一分钟,如果客户端在此期间没有收到任何消息,则服务器返回HTTP 204无内容。否则,只要有消息准备好发送给客户端,就会立即返回HTTP 200 OK的响应。

以下是令我感到困惑的事情:对于特定子集的客户端(总是网络连接不稳定),偶尔会出现这样的情况:客户端发出GET请求,服务器接收并处理GET请求,但客户端却一直处于等待状态。启用客户端的调试日志后,我发现HttpClient仍在等待响应的第一行。

服务器上没有抛出任何异常,至少没有任何日志记录,无论是Tomcat还是我的Web应用程序。根据调试日志显示,服务器成功地向客户端发送了响应。然而,客户端没有收到任何东西。客户端在HttpClient.executeMethod中无限期地挂起。这在会话超时后变得明显,并且客户端采取导致另一个线程发出HTTP POST的操作时。当然,POST会失败,因为会话已过期。在某些情况下,会话过期后到客户端发出POST并发现此事实之间可能已经过去了几个小时。在这整个时间里,executeMethod仍在等待HTTP响应行。

当我使用WireShark查看实际上正在发生的情况时,就不会出现这种故障。也就是说,在特定的客户端上,如果同时在两端运行WireShark,那么这些客户端将可以连续运行14个小时以上而不会出现故障。

大家有没有遇到过这种情况?这是什么原因造成的呢?我认为 TCP/IP 可以保证即使在短暂的网络故障中也能确保数据包的传递。如果我设置了 SO_TIMEOUT 并在超时后立即重试请求,那么重试总是成功的。(当然,我首先需要中止超时的请求并释放连接以确保使用新的套接字。)

有什么想法吗?有什么办法可以让 Java 或者 Windows 的注册表设置更加积极地进行丢失数据包的 TCP/IP 重试吗?


听起来观察正在改变结果 -> Heisenbug -> 线程存在问题。在这种情况下,听起来某人做得太快了(我猜是 HttpClient),因此导致死锁。有可能是 HttpClient 本身存在 bug,希望其他人能够提供更多帮助并解决这个问题。 - Esko
6个回答

8
您能肯定服务器已经成功将响应发送给似乎失败的客户端吗?这意味着服务器已经发送了响应并且客户端已经向服务器确认了该响应。您可以在服务器上使用Wireshark查看此情况。如果您确定服务器端已经出现这种情况,而客户端仍然没有看到任何东西,那么您需要从服务器上进一步查找。是否涉及任何代理/反向代理服务器或NAT?
TCP传输被认为是可靠的协议,但它不能保证传递。您的操作系统的TCP/IP堆栈将尝试通过TCP重传将数据包传递到另一端。如果正在发生这种情况,则应在服务器端的Wireshark中看到这些消息。如果看到过多的TCP重传,则通常是网络基础设施问题-即糟糕或配置错误的硬件/接口。 TCP重传对于短暂的网络中断非常有效,但在网络中断时间较长的情况下性能差。这是因为TCP/IP堆栈只会在定时器到期后才发送重传。此计时器通常在每次未成功重传后加倍。这是有设计意图的,以避免向已经存在问题的网络发送大量的重传。正如您所想象的那样,这通常会导致应用程序出现各种超时问题。
根据您的网络拓扑结构,您可能还需要在网络的其他中间位置放置探针/ Wireshark/tcpdump。这可能需要一些时间才能找出数据包的去向。
如果我是您,我会继续在所有端点上使用Wireshark进行监视,直到问题再次发生。它很可能会再次发生。但是,听起来您最终会发现所述的问题-不稳定的硬件。如果修复不稳定的硬件不可行,则可能需要在软件中构建额外的应用程序级超时和重试来尝试解决该问题。听起来您已经开始走下这条路了。

1
我从现场调试中能看到的是,我的Web应用程序认为它已经响应了。我没有在Tomcat(6.x)本身中启用任何调试来查看它是否认为已完成响应。Tomcat的日志、Apache HTTPD的日志和mod_jk的日志中都没有投诉。不稳定的硬件完全不在我的掌控范围内……在某些情况下,人们要穿越公共互联网。 - Eddie
没有什么比硬信息更重要了。Wireshark可以告诉你谁在说话,谁没有。 - Hans Malherbe

3
如果您正在使用长时间运行的GET请求,您应该在客户端上设置超时时间为服务器超时时间的两倍,正如您所发现的那样。
在TCP中,客户端发送消息并期望响应。如果服务器崩溃并重新启动(假设为了举例),则客户端仍将等待在套接字上从服务器获取响应,但服务器不再在该套接字上侦听。
只有当客户端在该套接字上发送更多数据并且服务器拒绝此新数据并关闭套接字时,客户端才会发现套接字已在服务器端关闭。
这就是为什么您应该在请求上设置客户端超时时间的原因。
但是,由于您的服务器没有崩溃,如果服务器是多线程的,并且线程套接字已关闭,但此时(持续几分钟)客户端出现连接故障,则可能会丢失最终套接字握手,由于您未向服务器发送更多数据,因此您的客户端再次被挂起。这将与您观察到的不稳定连接相吻合。

2

我并没有亲眼见过这个问题,但是我见过类似的问题:大UDP数据包导致IP分片,从而导致拥塞并最终丢失以太网帧。由于这是TCP / IP,我不认为IP分片会成为一个大问题,因为它是基于流的协议。

需要注意的一点是,TCP不保证传递!它不能保证。它所保证的是,如果您发送字节A,然后是字节B,那么在接收到字节A之前,你永远不会收到字节B

说完了,我建议连接客户机和监视机到交换机上。在监视机上运行Wireshark,你应该能够看到发生了什么。我遇到的问题与HTTP请求之间的空格处理和不正确的HTTP块大小有关。这两个问题都是由手写的HTTP堆栈引起的,所以只有在使用不稳定的堆栈时才会出现此问题。


2
忘记在主机端刷新或关闭套接字可能会间歇性地对短响应产生影响,具体取决于时间,这可能会受到任何监视机制的影响。特别是忘记关闭将使套接字悬空,直到GC回收它并调用finalize()。

0
这些电脑可能被安装了病毒/恶意软件吗?使用Wireshark安装WinPcap(http://www.winpcap.org/),它可能会覆盖恶意软件所做的更改(或者恶意软件可能会检测到被监视并且不尝试任何可疑操作)。

我之前没有考虑过这个,但当然有一定可能性。由于我只在网络连接不稳定的客户端看到这种情况,因此迄今为止我一直认为不稳定的网络连接本身是问题的原因。 - Eddie
1
恶意软件是远程可能的,但非常不太可能。按照您已经知道的 - 不稳定性。 - Gary

0

如果您正在丢失数据,很可能是由于读取或写入库中的软件错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接