【发布时间】:2014-06-15 10:33:06
【问题描述】:
我有一个应用程序使用 TCPClient 通过 TCP 套接字发送换行符终止的消息,它是底层 NetworkStream。
数据以大约每 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 秒),直到最终它全部赶上一个大镜头,然后循环重复。
我们通过最大化 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 meg 缓冲区已满,之后当 'end' 重置为 0 时,它会吸入流中剩余的所有内容,导致所有内容都赶上 - 但它不应该这样做是因为在我看来,逻辑看起来应该在下一次迭代时完全清空流(因为下一个 sizeToRead 仍然是 > 缓冲区中可用的数据)。
或者也许这是我没有想到的东西,我无法合成 - 但也许这里的那些聪明的灵魂可能会想到一些东西。
或者这可能是预期的行为 - 如果是,为什么?
【问题讨论】:
-
减小 sizeToRead 是一个错误,它应该始终等于缓冲区大小。
-
为什么一定要一样大?
-
因为读取少于您分配的缓冲区的内容是没有意义的。事实上,你抱怨它的行为方式,阅读越来越少,直到你突然重新设置它。
-
哦,但是当您不想踩踏之前的数据而不先将其复制到其他地方时,它确实有意义。我将尝试将其更改为始终读取 1 meg 的完整缓冲区,并在每次读取后将读取的内容复制到新缓冲区(这是上面设计不需要的副本,一个小的优化),让你知道它是否有帮助。
-
我认为独立程序和目标之间唯一不同的是 .net 框架是目标的 2.0,我在独立程序上运行 4 个客户端配置文件。此外,目标在较低优先级的线程上运行(不会因为传入的数据量而拖累 GUI 响应),但它只是低于正常。