在 Ubuntu 客户端(curl)和服务器之间的 XML 对话期间 TCP 重新传输导致超时

Posted

技术标签:

【中文标题】在 Ubuntu 客户端(curl)和服务器之间的 XML 对话期间 TCP 重新传输导致超时【英文标题】:TCP Retransmissions causing timeouts during XML conversation between Ubuntu client (curl) and server 【发布时间】:2020-07-12 03:55:21 【问题描述】:

我正在尝试确定并解决下面屏幕截图中概述的 ACK FIN 重传背后的原因。我正在使用 php 运行 ubuntu 18 apache 服务器,该服务器通过 PHP curl (PHP 7.3) 与 XML 服务提供商进行通信。在 XML 对话期间,有问题的服务器似乎随机超时。这会影响 xml 服务超时或返回不完整的 xml 结果集。 另一位开发人员告诉我他们有类似的问题,并使用 PHP 循环多次重试失败的请求,直到服务正确响应(从他们自己的客户端开发框)。这表明理论上服务器端可能有问题,但是在我排除所有可能性之前我无法接受这一点。(如果其他开发人员使用与我相同的客户端操作系统怎么办?等等)

我已经在本地 Windows 机器上通过邮递员进行了广泛的测试,但是我无法在这里发生超时/错误。不确定邮递员是否有某种纠错机制,或者我的 Windows 本身是否似乎更好地对抗服务器机器的 TCPIP 堆栈。

到目前为止我已经尝试过: - 将连接从 https 断开到 http,以便我可以在 Ubuntu 客户端上使用wireshark 进行捕获(同时排除 https 作为原因) - 将 MTU 从 9001(ec2 实例)分别更改为 1500 和 1492,问题仍然存在,服务服务器的 MTU 似乎是 1500 - 在 curl 上启用 keepalives,没有效果 - 在 curl 设置中尝试了不同的超时和连接超时,没有效果 - 尝试在 php 中使用相同的循环来重试请求。如果在 curl 内重试,则添加一个标志以完全切断 tcp 连接,没有效果,重试的请求有时仍会超时,有时它们会返回 xml 中预期的内容。看似随意。从 20 个请求中,可能有 2 个请求失败。

注意事项: 似乎在客户端发送一个 post xml 请求后,服务器响应一个 ACK​​,但从不发送状态 200,因为我在请求失败时捕获了这个。这似乎导致来自客户端的重复 FIN ACK 重传,这里似乎发生了一些错误更正,但是这不会冒泡到 XML 层来呈现完整的请求,而是 CURL 在等待响应时发出超时。在wireshark中,我可以看到不完整的答案,即在第42行重建了大约一半的xml。我唯一的预感是服务器可能是一个windows盒子,它可能与ubuntu的tcp ip堆栈不兼容,或者它只是一个错误除了重复请求之外,无论我做什么都可能无法修复的服务器上。

有什么想法吗?我不是 TCPIP 专家,所以 FINS 和 ACKS 只是一般理解:) 接下来你会尝试什么?

wireshark screenshot

【问题讨论】:

【参考方案1】:

有很多可以从网络捕获中解码。首先请注意,帧 46 和 47(后续 TCP 连接的开始,因为我们看不到相关连接的开始)协商的最大分段大小 (MSS) 为 1460。但是,帧 53 有 4380 字节(3 次MSS)和第 55 帧有 7300 字节(5 倍 MSS)。这很可能是因为网络捕获是在客户端主机上进行的,并且在 NIC 或驱动程序上启用了某种形式的接收卸载。一般来说,最好从网络上获取网络捕获(例如,通过跨越交换机端口并以混杂模式从跨越端口上的 NIC 获取捕获)。所以请记住,我们在捕获中看到的并不完全是网络上的内容。

我们看到正在考虑的请求在第 34 帧中发送,然后我们看到它在 11 毫秒后被确认(序列为 510)。

然后大约 20 秒内没有任何反应。这在 TCP 级别是完全正常的。尽我们所能知道没有传输未确认的数据(因此 TCP 不会关闭连接,即使启用 TCP 用户超时也不会起作用),所以这个连接可能会像这样闲置很长一段时间,直到 TCP keepalive 启动在(如果启用)。

然后,在第 36 帧中,客户端发送一个序列为 510 的 FIN。在 TCP 级别,这表明客户端已完成发送,这将由应用程序在套接字上调用 close()shutdown() 引起。由于这几乎是 20 秒,它确实感觉像是应用程序级别的超时,虽然它似乎不是 PHP Curl 的默认超时,但显然你一直在搞乱各种设置。

我们现在希望服务器确认传输的 FIN 并发送它需要发送的任何数据,然后当服务器应用程序调用 close()shutdown() 时,将从服务器发送一个 FIN。

但是,服务器并没有 ACK 客户端的 FIN,客户端 TCP 栈重传了 5 次 FIN。您可以看到每次 FIN 后重传时间加倍,假设它没有放弃,预计第 6 次重传时间约为 31.38 秒。在 Linux 上,我相信这是由 tcp_orphan_retries 控制的,默认为 8,所以我最好的猜测是它没有放弃。

最后,在第 42 帧中,服务器在预计下一次 FIN 重新传输之前开始讲话。它确认序列511,指示它接收到一个或多个FIN。并且它包含一个完整的 MSS 有效负载,从协议级别来看就很好。

那么现在的问题是,为什么服务器 TCP 堆栈直到现在才 ACK FIN?完整的 MSS 有效载荷是重新传输,还是第一次发送?让我们暂时停止猜测,因为捕获中还有其他有用的信息。

客户端立即以 RST 响应。这要么是因为 TCP 确实放弃了服务器并且连接不再存在,要么是因为应用程序在套接字上调用了 close(),因此 TCP 堆栈无法将数据传递给客户端应用程序,并且都希望有序关闭离开了。我猜是后者。

然后,令人惊讶的是,在第 44 帧中,我们从服务器获得了一个序列为 16061 的 FIN。 WAT???。第 42 帧发送了响应的前 1460 个字节,因此有 16060-1460 个字节的数据,总共丢失了 10 个 MSS。这里显然有丢包(这就是为什么 WireSharks 用“未捕获上一个段”来注释数据包的原因)。

我不认为 FIN 是重传,因为有大量未确认的数据需要重传(价值 11 MSS),而且 FIN 通常会在 close() 上迅速传输,即使有未确认的未确认数据数据。所以我猜这个 FIN 是在服务器在套接字上调用 close() 时发送的。

因此,如果 FIN 不是重传,我也猜测 10 毫秒前在第 42 帧中收到的前 1460 个字节可能不是那么好。我在想服务器做了它的想法,将 11 个 MSS 的数据写入套接字并关闭了套接字,导致 11 个带有有效负载的数据包(诚然,所有数据包都未确认,但 TCP“慢启动”的当代实现/配置将允许为此)其次是 FIN,但由于某种形式的数据包丢失,其中只有两个通过了。我进一步猜测 TCP 堆栈可能正在确认多个 FIN,但也可能会丢失数据包。

所以我的建议是增加 PHP Curl 的超时时间。我最好的猜测是服务器花了 20 多秒来计算它的回复,丢包和重传超时只会导致进一步的延迟。

【讨论】:

非常感谢您提供进一步的见解。我刚刚尝试将超时时间延长到 3000 秒。这应该足以让服务器完成它的工作,据我所知,服务器目前没有处于负载之下。您可以看到第 4 行的 POST 调用刚刚挂起,直到 3000 秒后客户端发送 FIN ACK 时达到超时。您会注意到来自第 4 行的相同 POST 调用在第 11 行重复,它需要大约 20 秒才能返回结果。通常这个调用平均需要大约 1 秒。 imgur.com/CUUdy7X @SashaM 当它在 3000 秒后超时,然后发送 FIN,它得到一个 RST,强烈建议另一端的连接已关闭。然而,我们没有从另一边看到 FIN。我猜响应数据包已经丢失,就像您之前的捕获一样。 @SashaM 我怀疑存在配置不当的路由器或负载平衡器。如果您想进一步了解这一点,我建议启用 TCP_KEEPALIVE,这似乎是 PhP Curl 支持的,并查看是否可以在上游进一步捕获数据包,以查看是否可以识别数据包丢失的来源。无论如何,您可能应该将超时设置为合理的值并在失败时重试。

以上是关于在 Ubuntu 客户端(curl)和服务器之间的 XML 对话期间 TCP 重新传输导致超时的主要内容,如果未能解决你的问题,请参考以下文章

如何在 php7.2 和 ubuntu 14 上添加 curl

PHP Curl 和 setcookie 问题

无法在 ubuntu 11.1 上将 curl 与 PHP 一起使用

服务器之间的 PHP cURL 问题,返回传输可能不会捕获响应

ubuntu 16.04 php 安装curl方法

linux系统ubuntu:用Qtcreater编程,需要实现两个主机之间的通讯,比如传输数据,应该怎样实现?