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

2024-10-04 03:27:49 发布

您现在位置:Python中文网/ 问答频道 /正文

我已经设置了一个简单的客户机服务器,从客户机发送的TCP数据包似乎没有到达服务器。在

正常情况下,一切正常,但当我在客户端上启动50个线程,用相同的小数据包(只有39个字节)“同时”撞击服务器时,服务器没有接收到所有字节的随机次数。更奇怪的是,它总是不接受它们。。。只接收5个字节。在

我使用tcpdumptcpflow来捕获两端发生的事情(如果不熟悉tcp流,它会从tcp流中删除大量的tcp SYN/ACK/FIN/etc噪声,并且只向您显示两个方向发送的数据)。在

在客户端,对于启动39字节数据包的50个线程来说,它看起来很完美。具体来说,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个都接收到这部分数据,之后my socket.recv超时:

^{pr2}$

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个字节。在

有什么关于这件事的根本原因的想法吗?我错过了什么?在


Tags: 数据服务器客户端客户机字节情况socket数据包
3条回答

回答我自己的问题。。。在

简单的回答是,仅使用TCP,客户机无法知道目标接收者是否真的收到了发送的字节。在

ie:客户端是否“高兴地”发送了字节并不重要。。。即使使用TCP,它们也可能永远不会到达,而且您肯定不知道它们何时到达预期的接收者。无论如何,如果不在应用程序层中构建一些确认,就不可能了。在

对于我的特殊情况,客户机发送的字节确实到达了服务器,但用了~30秒(!!!)此时客户端和服务器应用程序协议代码都已超时。在

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

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

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

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

更多详细信息供将来参考…

尽管您经常读到/听到TCP将:

  1. 保证您的数据包会到达(vsUDP,但不会)
  2. 保证你的包裹会按顺序到达

显然,第一个事实上根本不是真的。TCP将尽最大努力将您的字节发送给目标接收者(包括长时间重试),但这并不能保证,send man page是否为send返回值指明“一旦成功,这些调用将返回发送的字符数”。后者是而不是是真的,具有高度误导性(见下文)。在

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

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

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

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

附录…

我在这里留下的一个有趣的难题是,我是否需要在应用层协议中构建一些重试功能。目前看来,在服务器等待数据超时的情况下,关闭连接并用相同的请求打开一个新的连接是有益的。这似乎是因为低级TCP重试没有成功,但同时还有其他客户端线程及时通过。不过,这感觉非常错误。。。您可能认为TCP重试应该足够了。但事实并非如此。我需要找出TCP问题的根本原因来解决这个问题。在

您发送的字节数非常少,因此可能与Nagle algorithm相冲突,它将阻止您希望发送的数据,直到缓冲了合理数量的数据并准备好进行传输。在

创建完套接字后,在发送任何数据之前,请尝试添加以下行:

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

但请注意,这样做会带来更多的通信开销。在

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

相关问题 更多 >