当客户端愉快地发送它们时,为什么TCP数据包始终无法到达服务器?

时间:2012-04-20 16:07:51

标签: python c sockets tcp tcpdump

我设置了一个简单的客户端服务器,看起来我从客户端发送的TCP数据包没有到达服务器。

通常情况下一切正常,但是当我在客户端上启动50个线程以“同时”使用相同的小数据包(只有39个字节)同时命中服务器时,服务器无法全部接收所有线程字节。更奇怪的是,它在如何接收它们方面非常一致......只接收了5个字节。

我正在使用tcpdumptcpflow捕获两端发生的事情(如果不熟悉tcp流,它会删除大量的TCP SYN / ACK / FIN来自TCP流的/ etc噪声,只显示向任一方向发送的数据)

在客户端,50个线程触发39字节数据包,它看起来很完美。具体来说,tcpflow(使用libpcap)向我展示了50个相同的数据传输:

07 B6 00 01 | 00 1E 00 00 | <etc>

据我了解,libpcap / tcpdump从相当低的级别(低于TCP堆栈)获取数据,所以我认为这意味着数据发送正常,或者至少没有卡在内核缓冲区中。

但是,在查看服务器端时,一切都不完美。随机数失败,而且百分比很高。例如,在50个套接字连接中,30个工作正常,但是对于其中20个,我有一个协议失败,服务器的socket.recv超时等待字节(协议表明确切的数据包长度)。

非常如何失败。对于30/20的情况,30个插座完全接收传输的39个字节。其余20个ALL会收到此部分数据,之后我的socket.recv超时:

07 B6 00 01 | 00

对于20个连接中的每个连接只有5个字节到达,并且它似乎在内核级别,因为tcpdump仅显示5个字节到达。

这怎么可能发生?

这个5字节边界不是100%重合。它是标头的第一部分,接下来是34字节的有效负载,但是没有到达。在客户端,它是这样分开的。

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
sock.sendall(HEADER)  # 5 bytes
sock.sendall(PAYLOAD) #34 bytes

并且两个sock.sendall调用在每个线程中都成功完成,事实证明我的tcp日志记录显示所有50个运行完全发送39个字节“出门”。

关于这个根本原因的任何想法?我错过了什么?

3 个答案:

答案 0 :(得分:3)

回答我自己的问题......

简短的回答是,仅使用TCP,客户端无法知道预期收件人是否实际收到了发送的字节。

ie:客户端“愉快地”发送字节无关紧要......即使使用TCP,他们也可能永远不会到达,你绝对不知道何时

对于我的特定情况,事实证明客户端发送DID的字节实际到达服务器,但需要大约30秒(!!!)才能到达,此时客户端和服务器应用程序协议代码已经超时。

客户端和服务器端日志的视图(对于一个失败的连接)在这里:

这些图像是来自wireshark捕获文件的一个特定TCP流的tcpdump个视图。你可以看到发生了大量的重传。导致这些重新传输需求的根本原因是什么?我完全不知道(但很想知道!)。

数据在第二个最后一个条目(#974)到达服务器,在发送后约30秒,并且在它们之间有大量的重新传输尝试。如果对服务器端#793感到好奇,这是我的应用程序层协议尝试将消息发送回客户端,说“等待更多数据超时......它在哪里?”。

除了固有延迟之外,数据未出现在服务器的tcpdump日志中的原因之一似乎也是我对tcpdump的使用。简而言之:在查看捕获文件(使用tcpdump开关创建)之前,请确保从-w捕获中取出Ctrl-C,因为它似乎对您所看到的内容产生重大影响在文件中。我希望这是一个同花/同步问题,但我猜。但是,如果没有Ctrl-C,我肯定会缺少数据。

有关日后参考的详细信息......

虽然您经常阅读/听到TCP会:

  1. 保证您的数据包将到达(vs UDP,而不是
  2. 保证您的数据包按顺序到达
  3. 显而易见的是,第一个实际上根本不是真的。 TCP最好将您的字节数发送给预期的收件人(包括重试一段时间),但这不是保证,send man page是否表示send返回值“成功时,这些调用将返回发送的字符数“。后者是真的,并且具有很大的误导性(见下文)。

    其根源主要来自各种套接字调用(特别是send)的行为方式以及它们如何与操作系统的TCP / IP堆栈交互...

    在TCP交换的发送端,进展非常简单。首先是connect(),然后是send()

    connect()成功返回肯定意味着你能够建立与服务器的连接,所以你至少知道此时服务器在那里并且正在监听(即:3部分TCP打开握手是成功的)。

    对于'send`,虽然该调用的文档表明返回值(如果为正)是“发送的[字节数]”,这是完全错误的。返回值告诉您的所有内容是底层操作系统中的TCP堆栈接受其传出缓冲区的字节数。在此之后,操作系统将尽力将这些字节传递给您最初与之建立连接的收件人。但是这可能永远不会发生,所以它意味着你可以指望那些被发送的字节!有点令人惊讶的是,即使TCP内置了ACK消息,也没有真正的方法来确定是否发生了这种情况(或者没有!),至少在TCP套接字层发生了。要验证是否已完全接收已发送的字节,您需要在应用程序层添加某种确认。 nos在另一个问题中有a great answer,对此有所了解。

    <强>附录...

    我在这里留下的一个有趣的困境是我是否需要在我的应用层协议中构建一些重试功能。目前似乎,如果超时等待服务器上的数据,关闭连接并打开具有相同请求的新连接将是有益的。这似乎是这种方式,因为低级TCP重试不成功,但同时还有其他客户端线程正在及时通过。这感觉非常糟糕,但是......你会认为TCP重试应该足够了。但他们不是。我需要查看TCP问题的根本原因来解决这个问题。

答案 1 :(得分:3)

您发送的字节数非常少,因此您可能会违反Nagle algorithm,这将阻止您希望发送的数据,直到缓冲了大量数据并准备好被传播。

尝试在创建套接字后添加以下行,然后再发送任何数据:

sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)

但请注意,通过这样做,您将引入更多的通信开销。

答案 2 :(得分:1)

您必须非常小心,因为(由于缓冲)send和recv可能无法发送或接收您期望“应该”可用的数据。您还必须非常小心,任何线程都可以随时阻止,即使它“应该”能够接收到您认为发送的数据量。