【问题标题】:Socket.BeginReceive merging TCP packetsSocket.BeginReceive 合并 TCP 数据包
【发布时间】:2017-05-15 03:18:56
【问题描述】:

是否有一个设置可以用来防止将多个 TCP 数据包合并到 Socket.BeginReceive 回调内的单个缓冲区中?

你的下意识反应是我无法阻止 TCP 拆分/合并数据,但 这不是我要的;我可以清楚地看到在 Wireshark 中接收到的各个数据包,我唯一关心的是延迟,即在数据段到达时立即处理它。这并不意味着我不知道如何处理拆分/合并段,而是意味着我想避免延迟。

我的代码如下所示:

void WaitForData(ISocketInfo soc)
{
    if (socket != null && socket.Connected)
        socket.BeginReceive(buffer, 0, buffer.Length, 
            SocketFlags.None, OnPacketReceived, socket);
}

void OnPacketReceived(IAsyncResult asyn)
{
    try
    {
        var socket = (ISocketInfo)asyn.AsyncState;

        numberOfBytesReceived = socket.EndReceive(asyn);
        if (numberOfBytesReceived > 0)
        {
            _queue.Enqueue(buffer, 0, numberOfBytesReceived);
            OnNewDataReceived();
        }

        WaitForData(socketInfo);
    }
    catch (SocketException ex)
    {
        Log.Warn("Socket error while receiving packet", ex);
        Close();
    }
}

当我在 WireShark 中检查这些数据包时,我可以看到每 50 毫秒接收一次单独的 TCP 数据包,每个(比如说)100 字节。但有时在我的应用程序中会有 100 毫秒的延迟,而 OnPacketReceived 方法获得 200 字节。

既然 WireShark 确认这不是操作系统/网络问题,那么这里的问题可能是什么? OnPacketReceived 只是在后台线程上触发,因此它不会阻塞该方法,并且程序实际上并不会消耗太多 CPU。

(更新)

看来我的问题表达得不够清楚。我的问题不是如果数据被分割成段,如何解析数据。我的协议定义明确(即START_COOKIE、LENGTH、DATA、CRC),我一收到数据就将数据排入字节FIFO(上面sn-p中的_queue.Enqueue调用),所以我可以轻松解析它是异步的。

问题是,如果我看到没有数据包。在 Wireshark 中 +50ms 时为 1(100 字节),数据包编号2(100 字节)在 Wireshark 中 +100 毫秒,我的应用程序没有阻塞 OnPacketReceived 方法并且不消耗 CPU,.NET 怎么会每隔一段时间在 +100 毫秒时调用 OnPacketReceived 并合并两个数据包合二为一?

【问题讨论】:

  • 这里可能有什么问题?这里没有问题。 TCP 是一个无穷无尽的,您所描述的行为是标准的和预期的。这取决于您的编程方式来处理传入的部分“消息”以及一次接收的多个“消息”。使用字节长度前缀、消息分隔符或这些东西的组合。
  • This answer 提供了更多信息。
  • @Idle_Mind:我认为你误解了这个问题。无论如何,我将所有数据排队到 FIFO 缓冲区中,我可以轻松解析它,格式定义明确。但问题是:如果 Wireshark 显示网卡正在接收两个实际的单独 TCP 数据包,为什么 .NET 会产生额外的延迟并将两个数据包合并到一个缓冲区中?我担心的是延迟,而不是解析。其次,TCP 不是“无穷无尽的流”,它是一种用于分段传输(可能)无穷无尽的数据流的协议。
  • 很可能甚至没有 .NET 合并传入的数据。它可能已经发生在操作系统中。如果在短时间内接收到两个包含 TCP 连接数据的数据包,则它们的内容都将被放入 TCP 接收缓冲区,应用程序将简单地从该缓冲区中读取。它看不出这是一包还是两包。如果它偶尔发生一次,可能您的应用正忙于处理某些事情(垃圾收集?)并且在收到第二个数据包时没有获取第一个数据包。
  • TCP 堆栈将缓冲数据并处理flow control。它可以随意缓冲数据,也可以组合“数据包”,或者通过滑动窗口机制,在“数据包”中间停止数据流,即如果你的消息是 50 字节而接收窗口只有 28 字节,那么您可能会收到一小部分消息。所有这些都发生在 .NET 之下。 (“数据包”有点用词不当。由于段是从一个节点转发到另一个节点,它们可能会根据链接、缓冲区等进行拆分或组合。)

标签: c# .net sockets networking tcp


【解决方案1】:

TCP 根据定义是一个连续的数据流(它的主要特征是让它看起来好像根本没有数据包,不像 UDP)。因此,由您的软件来定义消息格式。你可以:

  • 用长度标记每条消息的开头(http 会这样做)。
  • 在邮件末尾添加分隔标记 (/n)。要让接收者知道何时开始和停止阅读单条消息,请标记消息的开头和结尾(使用无法键入的特殊符号)。

总而言之,您必须注意将流拆分为消息。或者在 TCP 之上使用 HTTP、WebSockets 或其他消息格式实现。

【讨论】:

    【解决方案2】:

    您可能会收到更少或更多,但不能保证您会收到发送的确切内容。

    来自 MSDN:

    无法保证您发送的数据会立即出现在网络上。为了提高网络效率,底层系统可能会延迟传输,直到收集到大量传出数据。 BeginSend 方法的成功完成意味着底层系统有空间来缓冲您的数据以进行网络发送。

    Here 是一篇很好的文章,可以解释这一点以及如何解决问题。

    【讨论】:

    • 谢谢,但这不是我问的。数据协议使用起始 cookie/长度/crc 进行了很好的定义,因此我将数据排队到 FIFO 中,并且无论在传输过程中如何拆分/合并它,我都会按顺序解析它。我的问题是:如果 Wireshark 显示网卡以 50 毫秒的延迟接收到两个实际的单独 TCP 数据包,那么 .NET(每隔一段时间)如何跳过传送第一个数据包,创建一个额外的延迟,然后合并两个数据包到单个缓冲区,而应用程序实际上没有做任何事情来阻止即时交付?
    猜你喜欢
    • 2018-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-06
    • 1970-01-01
    • 2011-04-17
    • 2020-03-13
    相关资源
    最近更新 更多