在进行网络I / O时是否缓冲了Stream.Read?

所以我最近做了一些工作,当有人告诉我,如果在网络流上做一个Stream.Read是通过在WebResponse上调用.NET的一个GetResponseStream获得的,或者那些是缓冲的。

他说如果你要在你正在阅读的代码中设置一个断点,你就不会停止网络流量。 我发现这很奇怪,但也希望这是真的。 这是如何运作的? 它甚至准确吗?

 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对象未缓冲。

第二部分(关于设置断点)的简短回答是你的同事不正确。 网络流量将停止,但最终会形成“最终”,请继续阅读以获取更多详细信息。

Bing为“SO_RCVBUF”,“tcp接收窗口大小”,“vista自动缩放”,用于更一般的信息。

详细部分

让我们从这开始,这是Windows网络堆栈的文本视图:

++ .NET Network API

++ — Winsock DLL(用户模式)

++ —— afd.sys(内核模式)

++ ——— tcpip.sys

++ ———— ndis

++ —————网络接口(hal)

这是一个粗略的堆栈,掩盖了一些细节,但一般的想法是.NET调用Winsock用户模式dll,然后将大部分实际工作推送到它的表兄AFD (辅助function驱动程序),然后再转到tcpip sub系统,等等..

AFD级别,有一个缓冲区,通常介于8K和64K之间,但是对于Vista(及更高版本),它也可以扩展。 此设置也可以通过注册表设置( HKLM \ SYSTEM \ CurrentControlSet \ services \ AFD \ Parameters )进行控制。

此外,tcpip.sys还有一个缓冲区,类似于AFD的缓冲区。 我相信打开套接字时传递的* SO_RCVBUF *设置也可以改变它。

基本上,当您接收数据时,代表您的tcpip.sys会不断获取数据,并保持告知发送方它获取数据( ACK ),并且直到其缓冲区已满为止。 但与此同时, afd.sys通过询问数据(然后将其复制到自己的缓冲区)来清除tcpip.sys缓冲区,因此tcpip.sys可以从发送方填充更多数据。

然后就是你(.NET API调用者),他也在做同样的事情,调用Read()方法并将数据复制到缓冲区中。

所以,如果你考虑一下,一条256Kb的消息通过线路传输,64K位于tcpip.sys缓冲区,64K位于afd.sys缓冲区中,并且在请求一个4K(你的bufferSize变量)块之后设置一个断点,我们正在查看收到的128K ACK’ed回发送器,并且由于tcpip.sys缓冲区已满(假设64K大小)现在(并且您被调试会话阻止), tcpip.sys将没有选项但要告诉发送方停止通过网络发送字节数,因为它无法足够快地处理它们。

实际上(即有人没有设置断点!),我已经看到GC引发这种行为。 看到一个3秒垃圾收集的情况,让所有操作系统缓冲区都填满。

这是准确的。 TCP由Windows TCP / IP驱动程序堆栈实现。 在程序中设置断点不会阻止驱动程序从服务器下载数据。 直到驱动程序决定使用太多内核池空间来缓冲数据。 具体规则未记录。

这是一种优化,是操作系统中的标准优化。 该策略使TCP传输非常有效,并不是因为程序的响应速度,而是仅通过连接的带宽以及驱动程序堆栈对网卡中断的响应速度。 它非常擅长,这是一个司机的工作。

默认情况下, NetworkStream不会缓冲。 当您在正在读取此流的过程中放置​​断点时,正在向底层套接字发送数据的客户端将阻塞并等待,直到远程套接字再次准备好接收为止。 客户端将无法写入套接字,因此,是的,网络流量停止

这是一篇博客文章 ,演示了如何使用BufferedStream类缓冲它。