在执行网络 I/O 时是否缓冲了 Stream.Read
本文关键字:缓冲 是否 Stream Read 执行 网络 | 更新日期: 2023-09-27 18:20:45
所以我最近在做一些工作,当时有人告诉我,如果在网络流上做一个Stream.Read
,这是通过调用其中一个获得的。NET 的GetResponseStream
WebResponse
或缓冲的那些。
他说,如果你要在你正在读取的代码中放置一个断点,你不会停止网络流量。我觉得这很奇怪,但也希望这是真的。这是怎么回事?它甚至准确吗?
using (Stream webResponseStream = this.webResponse.GetResponseStream())
{
byte[] readBuffer = new byte[bufferSize];
int bytesRead = webResponseStream.Read(readBuffer, 0, bufferSize);
while (bytesRead > 0)
{
bytesRead = webResponseStream.Read(readBuffer, 0, bufferSize);
// If I put a breakpoint here, does network activity stop?
}
}
否,GetResponseStream 返回的 Stream 对象未缓冲。
对第二部分(关于设置断点(的简短回答是你的同事不正确。网络流量将停止,但最终会停止,要描述"最终",请继续阅读以获取更多详细信息。
必应"SO_RCVBUF"、"TCP 接收窗口大小"、"远景自动缩放",以获取更多常规信息。
详细部分
让我们从这个开始,下面是 Windows 网络堆栈的文本视图:
++ .NET Network API 的
++ --- Winsock DLL(用户模式(
++ ------ AFD.sys(内核模式(
++ --------- TCPIP.sys
++ ------------ NDIS
++ ---------------网络接口 (HAL(
这是一个粗略的堆栈,掩盖了一些细节,但一般的想法是.NET调用Winsock用户模式dll,然后将大部分实际工作推送到其表亲AFD(辅助功能驱动程序(,然后推送到tpip子系统,依此类推。
在AFD级别,有一个缓冲区,通常在8K和64K之间,但对于Vista(及更高(,它也可以向上扩展。此设置也可以由注册表设置(HKLM''SYSTEM''CurrentControlSet''services''AFD''Parameters(控制。
此外,tcpip.sys还有一个缓冲区,类似于AFD的缓冲区。我相信打开插座时通过的*SO_RCVBUF*设置也可以改变这一点。
从本质上讲,当您接收数据时,tcpip.sys代表您不断获取数据,并告诉发送方它获得了数据(ACK(,并一直这样做,直到其缓冲区已满。但与此同时,afd.sys 通过向 tcpip.sys 请求数据(然后将其复制到自己的缓冲区中(来清除 tcpip,因此 tcpip.sys 可以从发送方填充更多数据。
然后是你(.NET API 调用者(,他也在做同样的事情,调用 Read(( 方法并将数据复制到缓冲区中。
所以,如果你考虑一下,一条 256Kb 的消息通过网络发送,64K 位于 tcpip.sys 缓冲区中,64K 位于 afd.sys 缓冲区中,并且在请求一个 4K(您的缓冲区大小变量(块后设置断点,我们正在查看收到的 128K ACK 返回给发送方,并且由于 tcpip.sys 缓冲区现在已满(假设大小为 64K((并且您被调试会话阻止(, TCPIP.sys别无选择,只能告诉发送方停止通过网络发送字节,因为它无法足够快地处理它们。
实际上(即有人没有设置断点!(,我已经看到GC诱导了这种行为。看到一个 3 秒垃圾回收的情况,让所有操作系统缓冲区都填满。
准确的。 TCP 由 Windows TCP/IP 驱动程序堆栈实现。 在程序中设置断点不会阻止驱动程序从服务器下载数据。 直到驱动程序确定使用过多的内核池空间来缓冲数据。 其确切规则未记录。
这是一种优化,是操作系统中的标准优化。 该策略使TCP传输非常高效,它不取决于程序的响应速度,而仅取决于连接的带宽以及驱动程序堆栈对网卡中断的响应程度。 它非常擅长,这是司机的工作。
默认情况下不缓冲网络流。在读取此流的过程中放置断点时,将数据发送到基础套接字的客户端将阻塞并等待远程套接字准备好再次接收。客户端将无法写入套接字,因此,是的,网络流量停止。
下面是一篇博客文章,它说明了如何使用 BufferedStream 类对其进行缓冲。