我已经建立了一个简单的客户端服务器,好像我从客户端发送的TCP数据包没有到达服务器。

通常,一切正常,但是当我在客户端上启动50个线程以使用相同的小数据包(仅39个字节)“同时”访问服务器时,服务器没有随机次数接收所有字节。甚至更陌生的是,它在如何不接收它们方面也非常一致……仅接收了5个字节。

我正在使用tcpdumptcpflow捕获两端发生的情况(如果不熟悉tcp流,它将消除TCP流中的大量TCP SYN/ACK/FIN/etc噪声,并仅向您显示发送的数据任一方向)。

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

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

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

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

在失败方式上非常一致。对于30/20情况,其中30个套接字完美地接收了传输的39个字节。其余20个ALL接收此部分数据,此后我的socket.recv超时:
07 B6 00 01 | 00

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

怎么会这样

这5个字节的边界不是100%一致的。它是 header 的第一部分,紧随其后是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个字节。

关于此问题的根本原因有任何想法吗?我想念什么?

最佳答案

回答我自己的问题...

简短的答案是,仅凭TCP,客户端就无法知道目标接收者是否实际上已接收到发送的字节。

即:客户端是否“愉快地”发送了字节都没关系……即使使用TCP,它们也可能永远不会到达,并且您绝对不知道何时将它们发送到预期的接收者。无论如何,并非没有对应用程序层进行一些确认。

对于我的特殊情况,事实是客户端发送的DID字节实际上到达了服务器,但是花了大约30秒(!!!)到达,此时客户端和服务器应用程序协议(protocol)代码都已超时。

客户端和服务器端日志(对于一个失败的连接)的 View 在此处:

  • Client side TCP log
  • Server side TCP log

  • 这些图像是wireshark捕获文件中一个特定TCP流的tcpdump View 。您可以看到发生了很多重传。促使这些重新传输需求的根本原因是什么?我完全不知道(但是很想知道!)。

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

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

    更多详细信息,以备将来引用...

    尽管您经常阅读/听到TCP会:
  • 确保您的数据包将到达(与UDP相对)
  • 确保您的数据包按
  • 的顺序到达

    很明显/很明显,第一个实际上是不正确的。 TCP会尽力将您的字节发送给预期的收件人(包括重试较长时间),但这不是保证,无论send man page是否指示send返回值“成功时,这些调用都会返回该数字发送的字符数”。后者是不正确的,并且具有高度误导性(请参见下文)。

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

    在TCP交换的发送方,过程非常简单。首先是connect(),然后是send()
    connect()成功返回绝对意味着您能够建立与服务器的连接,因此您至少知道此时服务器已在那里并正在监听(即,由三部分组成的TCP打开握手成功)。

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

    附录...

    我剩下的一个有趣的难题是我是否需要在我的应用程序层协议(protocol)中内置一些重试功能。当前看来,如果超时在服务器上等待数据,关闭连接并打开具有相同请求的新连接将是有益的。出现这种情况是因为低级别的TCP重试未成功,但是与此同时,还有其他客户端线程正在及时通过。但是,这感觉非常错误,您可能会认为TCP重试就足够了。但事实并非如此。我需要研究TCP问题的根本原因以解决此问题。

    关于python - 为什么当客户端愉快地发送TCP数据包时,TCP数据包始终无法到达服务器?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/10249677/

    10-15 23:32