什么原因会导致 NetworkStream.Read 被阻塞或挂起?

11

MSDN文档似乎表明NetworkStream.Read将总是立即返回。如果没有找到数据,则返回0。但是,我有一些代码当前正在运行,只有在某些情况下(我还没有弄清楚哪些情况),NetworkStream.Read似乎会挂起。以下是我从转储文件中收集到的堆栈跟踪:

00000000705ae850 000007fef784f60d DomainBoundILStubClass.IL_STUB(IntPtr, Byte*, Int32, System.Net.Sockets.SocketFlags)
00000000705ae930 000007fef785c930 System.Net.Sockets.Socket.Receive(Byte[], Int32, Int32, System.Net.Sockets.SocketFlags, System.Net.Sockets.SocketError ByRef)
00000000705ae9b0 000007ff004eb668 System.Net.Sockets.NetworkStream.Read(Byte[], Int32, Int32)
00000000705aea40 000007fef784e6ae MySocketStuff.SocketConnectCallback(System.IAsyncResult)
00000000705aeb20 000007fef84f2bbb System.Net.LazyAsyncResult.Complete(IntPtr)
00000000705aeb90 000007fef7853c7b System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
00000000705aebe0 000007fef784e5d3 System.Net.ContextAwareResult.Complete(IntPtr)
00000000705aec40 000007fef7d027f9 System.Net.LazyAsyncResult.ProtectedInvokeCallback(System.Object, IntPtr)
00000000705aeca0 000007fef8b9815e System.Net.Sockets.Socket.ConnectCallback()
00000000705aed20 000007fef93e14c2 System.Threading._ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(System.Object, Boolean)

我注意到NetworkStrea.Read实际上调用了Socket.Receive,根据我的理解,这可能会阻塞。只是我不知道为什么有时它会阻塞,有时它则不会。

4个回答

32
该段文档中 NetworkStream.ReadRemarks 部分存在误导。它说:

该方法将数据读入缓冲区参数,并返回成功读取的字节数。如果没有可读数据,则 Read 方法返回 0。Read 操作读取所有可用的数据,最多达到大小参数所指定的字节数。如果远程主机关闭连接并且已接收到所有可用数据,则 Read 方法立即完成并返回零字节。

应改为:

该方法将数据读入缓冲区参数,并返回成功读取的字节数。如果没有可读数据,则 Read 方法阻塞直到有数据可读或连接关闭。Read 操作读取所有可用的数据,最多达到大小参数所指定的字节数。如果远程主机关闭连接并且已接收到所有可用数据,则 Read 方法立即完成并返回零字节。


但是文档上说的是相反的... "该方法将数据读入缓冲区参数并返回成功读取的字节数。如果没有可供读取的数据,则Read方法返回0" - Mark
当“不会再有可读取的数据”时,它返回0。例如,如果存在断开连接的情况。否则,该方法将阻塞。 - Mark H
我注意到它在.NET 4.0上会阻塞,在Azure的计算模拟器中,但在Azure中不会。 多方便啊!你永远不知道会发生什么... - Fabrice
2
那不是“误导”,那只是明显的“错误”。 - J...
如果套接字已关闭,则Read方法返回0。 Read操作读取尽可能多的可用数据,最多到size参数指定的字节数。 如果远程主机关闭连接,并且接收到所有可用数据,则Read方法立即完成并返回零字节。该段落已经在新文档平台上更改,以使其听起来不那么刺耳(https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.networkstream.read#Remarks)。 - Ray
在我的情况下,我添加了ReadTimeout以避免在读取函数时无限等待。 - aerobrain

7

假设套接字缓冲区中有数据,有时会出现数据,有时不会。

看到 NetworkStream 阻塞的一个常见原因是每个连接的一侧都期望对方关闭。例如,如果您建立了一个 HTTP 1.1 keep-alive 连接,但仍然使用“读取直到连接关闭”的方式获取内容。


5

在处理NetworkStream时常见的错误是通过Write方法发送未完成的命令,导致连续调用Read方法出现挂起。

看下面的例子,它试图向一个已打开的FTP端口发送用户名。它期望得到一个响应,如 331请指定密码 ,但Read方法却一直挂起:

var request = Encoding.ASCII.GetBytes("user [username]");
networkStream.Write(request, 0, request.Length);
var streamReader = new StreamReader(networkStream);
var response = streamReader.ReadLine(); // <-- hangs

一个神奇的解决方案是将第一行替换为以下内容:
var request = Encoding.ASCII.GetBytes("user [username] \r\n");

只需在命令的末尾添加\r\n短语,一切都会按预期开始工作。


1
这个救了我的一天。 - Jacky
这是一个好答案,因为它强调了问题可能在上面的行中。 - S Meaden
这可能就是答案!太好了找到了! - colin lamarre

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接