为什么NetworkStream会这样读?

时间:2014-04-29 13:04:00

标签: c# tcp stream tcpclient

我有一个应用程序,它使用TCPClient及其底层NetworkStream发送通过TCP套接字终止换行的消息。

数据从实时数据流每隔100毫秒流入大约28k用于监控。

我已经删除了不相关的代码,这基本上是我们读取数据的方式:

TcpClient socket; // initialized elsewhere
byte[] bigbuffer = new byte[0x1000000];
socket.ReceiveBufferSize = 0x1000000;
NetworkStream ns = socket.GetStream();
int end = 0;
int sizeToRead = 0x1000000;
while (true)
{
  bytesRead = ns.Read(bigbuffer, end, sizeToRead);
  sizeToRead -= bytesRead;
  end += bytesRead;

  // check for newline in read buffer, and if found, slice it up, and return
  // data for deserialization in another thread

  // circular buffer
  if (sizeToRead == 0)
  {
    sizeToRead = 0x1000000;
    end = 0;
  }
}

我们看到的症状,有点间歇性地基于我们发回的数据量,是会有一个“延迟”。记录,我们从流中读取的数据逐渐变得越来越老,而不是我们提供的数据(流式传输几分钟后,滞后时间为10秒),直到最终全部赶上在一个重要的镜头,循环重复。

我们通过maxing out sizeToRead来修复它,并且(无论这是否是必需的,我不确定,但我们无论如何都这样做了),删除了TcpClient上设置的ReceiveBufferSize并将其保持为默认值8192(更改)只是ReceiveBufferSize没有纠正它。)

int sizeForThisRead = sizeToRead > 8192 ? 8192 : sizeToRead;
bytesRead = ns.Read(bigBuffer, end, sizeForThisRead);

我想也许这是与nagle和延迟ack的交互,但wireshark表明数据根据时间戳并查看数据(带时间戳,服务器和客户端时钟在秒)。

我们在ns.Read之后输出日志,并且肯定问题出在Read调用而不是反序列化代码。

所以这让我相信,如果你设置TcpClient的ReceiveBufferSize真的很大,并且在你的Read调用它的底层NetworkStream传递bytesToRead比预期到达的字节多得多,在Read呼叫中等待那些字节到达的超时,但它仍然没有返回流中的所有内容?此循环中的每个连续调用都是超时,直到1兆的缓冲区已满,之后“结束”时间结束。重置为0,它会吸引流中剩下的所有内容,导致它们全部赶上 - 但它不应该这样做,因为逻辑对我来说看起来应该完全清空流下一次迭代(因为下一个sizeToRead仍然是>缓冲区中可用的数据)。

或者也许是我没想到我无法合成的东西 - 但也许那些聪明的灵魂可能会想到什么。

或许这是预期的行为 - 如果是这样,为什么?

2 个答案:

答案 0 :(得分:6)

这种行为非常有趣,我只能亲眼看到它,而且......我做不到。

这个 -answer提出了一个替代理论,可以解释问题中描述的滞后。我不得不从问题和评论中推断出一些细节。

目标应用程序是一个交互式UI应用程序,具有三个操作线程:

  1. TcpClient网络数据使用者。
  2. 将结果传递给UI的数据队列使用者线程。
  3. UI线程。
  4. 出于本讨论的目的,假设TheDataQueueBlockingCollection<string>实例(任何线程安全队列都可以):

    BlockingCollection<string> TheDataQueue = new BlockingCollection<string>(1000);
    

    应用程序有两个同步操作,在等待数据时阻塞。第一个是NetworkStream.Read电话,这是该问题的主要主题:

    bytesRead = ns.Read(bigbuffer, end, sizeToRead);
    

    当工作队列中的数据被编组到UI以供显示时,会发生第二个阻塞操作。我们假设代码如下所示:

    // A member method on the derived class of `System.Windows.Forms.Form` for the UI.
    public void MarshallDataToUI()
    {
        // Current thread: data queue consumer thread.
        // This call blocks if the data queue is empty.
        string text = TheDataQueue.Take();
    
        // Marshall the text to the UI thread.
        Invoke(new Action<string>(ReceiveText), text);
    }
    
    private void ReceiveText(string text)
    {
        // Display the text.
        textBoxDataFeed.Text = text;
    
        // Explicitly process all Windows messages currently in the message queue to force
        // immediate UI refresh.  We want the UI to display the very latest data, right?
        // Note that this can be relatively slow...
        Application.DoEvents();
    }
    

    在此应用程序设计中,当网络将数据传送到TheWorkQueue的速度超过UI可以显示的速度时,就会出现观察到的延迟。

    为什么@ paquetp的日志会显示NetworkStream.Read的问题?

    NetworkStream.Read阻止,直到数据可用。如果日志在等待更多数据时报告经过的时间,则会出现明显的延迟。但是TcpClient网络缓冲区实际上是空的,因为应用程序已经读取并排队了数据。如果实时数据流是突发性的,那么这种情况经常发生。

    你怎么解释最终它全部赶上了一个重要的

    这是数据队列使用者线程处理TheDataQueue中的待办事项的自然结果。

    但是数据包捕获和数据时间戳呢?

    如果项目在TheDataQueue中积压,则数据时间戳是正确的。但是你还没有在UI中看到它们。数据包捕获时间戳是及时的,因为网络数据已被网络线程接收并快速排队。

    这不仅仅是猜测吗?

    不。有一对自定义应用程序(生产者和消费者)可以证明这种行为。

    Network Consumer App Screenshot

    屏幕截图显示数据队列积压了383项。数据时间戳滞后于当前时间戳约41秒。我暂停了生产者几次以模拟突发网络数据。

    然而,我永远无法让NetworkStream.Read表现出所谓的问题。

答案 1 :(得分:1)

TcpClient.NoDelay属性获取或设置一个值,该值在发送或接收缓冲区未满时禁用延迟。

NoDelayfalse时,TcpClient在收集大量传出数据之前不会通过网络发送数据包。由于TCP段中的开销量很大,因此发送少量数据效率很低。但是,确实存在需要发送非常少量数据或期望从您发送的每个数据包立即响应的情况。您的决定应权衡网络效率与应用要求的相对重要性。

来源:http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.nodelay(v=vs.110).aspx

按位解释 默认情况下,当满足以下条件之一时,Windows Server 2003 TCP / IP完成recv()调用:

  1. 数据以PUSH位设置
  2. 到达
  3. 用户recv缓冲区已满
  4. 自任何数据到达后已经过了0.5秒
  5. 如果客户端应用程序在具有未在发送操作上设置推送位的TCP / IP实现的计算机上运行,​​则可能导致响应延迟。最好在客户端纠正这个问题;但是,配置参数(IgnorePushBitOnReceives)被添加到Afd.sys以强制它处理所有到达的数据包,就像设置了推送位一样。

    尝试减小缓冲区大小以强制供应商网络实现设置PSH位。

    来源:http://technet.microsoft.com/en-us/library/cc758517(WS.10).aspx(在推位解释下) 来源:http://technet.microsoft.com/en-us/library/cc781532(WS.10).aspx(在IgnorePushBitOnReceives下)