在进行网络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类缓冲它。