socket.send 即使在 Tcpclient.NoDelay = true 之后也会将数据包附加到一起

本文关键字:数据包 到一起 之后 true send Tcpclient NoDelay socket | 更新日期: 2023-09-27 18:33:58

这开始让我感到非常沮丧,因为无论我做什么,我的数据包都会被推到一起,这使得不得不在另一端区分它们很烦人(对象很容易,可变字符串可能很痛苦(。有没有办法避免这种情况?

这是我正在使用的一些粗略代码(是的,初始化在这里发生,这是粗略的测试代码,很抱歉编程约定不佳(

    sender.Connect("localhost", 8523);
    sender.Client.SendTimeout = 1000;
    sender.NoDelay = true;
    byte[] buffer = ASCIIEncoding.ASCII.GetBytes(message);
    sender.Client.Send(buffer);
    buffer = ASCIIEncoding.ASCII.GetBytes("DISCONNECT");
    sender.Client.Send(buffer);
    sender.Client.Close();

如您所见,两个发送一个接一个地发生,套接字决定将它们一起发送是最佳选择。那太好了,但我不想要这样。因此,在另一端,它最终是

   helloDISCONNECT

我需要分开接收两者,因为它们会自动加载到阻塞队列中,并且我不想处理对象大小和字符串解析。

socket.send 即使在 Tcpclient.NoDelay = true 之后也会将数据包附加到一起

TCP 向接收方呈现字节流。

在流关闭之前,TCP 套接字的每次读取都可以返回介于 1 和您为其提供的缓冲区大小之间。您应该相应地编写代码。

TCP不知道也不关心程序的消息框架概念。它不在乎您发送 3 条带有单独调用的"消息"来发送。有效的 TCP 实现可以为每次调用从流中读取的函数返回一个字节,也可以在将字节返回给您之前尽可能多地缓冲。您应该相应地编写代码。

如果您想在通过TCP发送时处理消息,则必须应用自己的框架,并且在阅读消息时必须自己处理该框架。这通常意味着已知大小的长度前缀或消息终止符。

我在这里更详细地处理了这一点,尽管代码在C++但这些概念仍然适用。

您似乎期望:

sender.NoDelay = true;

本质上意味着:

sender.Client.Send(buffer);

。成为阻塞调用,直到位从 NIC 中断开。不幸的是,情况并非如此。 NoDelay 只告诉网络层不要等待,但您使用 Send 进行的基础调用具有一些异步行为,并且程序的线程运行速度足够快,以至于在您基本上附加第二个字符串之前,网络层的线程不会唤醒。

在此处阅读有关NoDelay的更多信息。有关详细信息,请阅读那里的备注。

一些相关的报价:

获取或设置一个布尔值,该值指定流套接字 正在使用内格尔算法。

Nagle算法旨在通过导致 套接字缓冲小数据包,然后组合并发送它们 特定情况下一包

此外,即使你可以承诺离开这些位,客户端的网络层也可能在你的客户端代码有机会看到有两个"数据包"之前缓冲它们。

除了sender.NoDelay = true之外,对我绝对有用的技巧是在sender.Client.Send(...)后以Thread.Sleep(50)的形式使用额外的延迟。

实际数字没有经过研究,它只是碰巧从一开始就工作(我估计 50 毫秒是一个合理的等待时间,而不会在我的发送队列中创建积压工作(,所以一定要针对您的操作环境进行测试和调整。

题外话:接收我的数据的应用程序是一个闭路电视录像机,它可以在视频上覆盖文本,以便每个TCP数据包在屏幕上都是不同的行。我被迫对 Nagle 的算法做点什么,否则我的"线条"就会被"堆积"在一起(更不用说显然与机器使用缓冲区有关的其他工件(。

如果要确定在 TCP 上单独接收具有上述约束的消息,则应在每条消息后关闭连接。如果您不希望收到回复,则可以对读取和写入执行关闭操作,如果需要回复,请仅对写入执行关闭操作。关闭写入将导致接收方接收 0 字节,因此接收方可以检测到消息结束。例如,http 的工作原理是这样的。

这里的权衡是,您需要为每条消息打开一个新的TCP连接,这会导致一些开销。但是,在许多应用中,这并不重要。

免责声明:这很容易通过BSD套接字API实现。我对 C# 的经验很少,也不知道它是否提供了合适的 API 来执行这些低级套接字操作。