为什么NetworkStream会这样读?

paq*_*etp 11 c# tcp stream tcpclient

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

数据从实时数据流每100ms流入大约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;
  }
}
Run Code Online (Sandbox Code Playgroud)

我们看到的症状,有点间歇性地基于我们发回的数据量,是会有一个"滞后"的记录,我们从流中读取的数据逐渐变得越来越老,而不是我们的交付(在流动几分钟之后,滞后时间为10秒),直到最终全部赶上一个大镜头,并且循环重复.

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

int sizeForThisRead = sizeToRead > 8192 ? 8192 : sizeToRead;
bytesRead = ns.Read(bigBuffer, end, sizeForThisRead);
Run Code Online (Sandbox Code Playgroud)

我想也许这是与nagle和延迟ack的交互,但wireshark表明数据基于时间戳并查看数据(有时间戳,服务器和客户端时钟在一秒钟内同步)进入正常状态.

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

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

或者也许这是我没想到的,我无法合成的东西 - 但也许那些聪明的灵魂可能会想到一些东西.

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

Joe*_*son 6

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

这种反向解决方案提供了另一种理论,可以解释问题中描述的滞后.我不得不从问题和评论中推断出一些细节.

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

  1. 一个TcpClient网络数据消费.
  2. 数据队列使用者线程,用于将结果传递给UI.
  3. UI线程.

出于本讨论的目的,假设这TheDataQueue是一个BlockingCollection<string>实例(任何线程安全队列都会这样做):

BlockingCollection<string> TheDataQueue = new BlockingCollection<string>(1000);
Run Code Online (Sandbox Code Playgroud)

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

bytesRead = ns.Read(bigbuffer, end, sizeToRead);
Run Code Online (Sandbox Code Playgroud)

当工作队列中的数据被编组到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();
}
Run Code Online (Sandbox Code Playgroud)

在此应用程序设计中,当网络以TheWorkQueue比UI显示数据更快的速度传送数据时,会出现观察到的延迟.

@ paquetp的日志为什么会出现问题NetworkStream.Read

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

你怎么解释最终这一切都赶上了一个大人物

这是数据队列使用者线程通过积压工作的自然结果TheDataQueue.

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

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

这不仅仅是猜测吗?

不.有一对自定义应用程序(生产者和消费者)可以演示此行为.

网络消费者应用截图

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

但是,我从来没有能够NetworkStream.Read像所谓的问题那样表现出来.


mad*_*ang 1

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

NoDelay是时false,aTcpClient不会通过网络发送数据包,直到它收集到大量传出数据。由于 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. 自任何数据到达以来已过去 0.5 秒

如果客户端应用程序在具有 TCP/IP 实现的计算机上运行,​​而该实现未在发送操作上设置推送位,则可能会导致响应延迟。最好在客户端纠正这个问题;但是,Afd.sys 中添加了一个配置参数 (IgnorePushBitOnReceives),以强制其将所有到达的数据包视为设置了推送位。

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

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