【问题标题】:System.IO.Stream.Read gets stuckSystem.IO.Stream.Read 卡住
【发布时间】:2011-11-17 15:53:44
【问题描述】:

我使用 System.IO.Stream.Read 与服务器端建立了 HTTP 连接,以读取 HTTP 请求正文消息。 问题是,在几分钟内,服务器会卡在 Read 语句上,并且在达到套接字超时或客户端关闭连接之前不会继续。

int bytesRead = 0;

while (bytesRead < contentLength)
{
  int got = stream.Stream.Read(buffer.Buffer, bytesRead, contentLength - bytesRead);
  bytesRead += got;
}
  1. 如果流没有 contentLength 变量指定的数据量,可能会发生这种情况。 情况并非如此,因为当使用 WireShark 跟踪 tcp 流时,我看到整个消息正文(由 contentLength 指定)已到达服务器计算机。

  2. 仅在第一次“使用”while 循环时发生,即仅在流没有“contentLength”字节数的情况下一次尝试和 while 读取必须重新进入循环。

为什么会卡住不继续读取数据?

【问题讨论】:

  • 可能是服务器会话时间限制。
  • 您在 HttpWebRequest 类上使用 System.io.Stream 的任何原因?好像有点不方便?
  • @kmcc049 关键字是“body”——通过Stream 读/写body 是很常见的;最常见的是HttpRequest.InputStream,或者如果您正在创建一个请求,HttpWebRequest.GetRequestStream()
  • @Marc ah ok 没注意正文部分,以为他在手动做 http 的东西,像 using (var remoteReader = new StreamReader(remoteResponse.GetResponseStream())) result = remoteReader.ReadToEnd(); 这样的东西阅读正文似乎更方便....

标签: c# .net sockets tcp stream


【解决方案1】:

我想知道流是否报告提前终止;您还应该查看Read 是否返回了一个非正数,即

while (bytesRead < contentLength)
{
  int got = stream.Stream.Read(
       buffer.Buffer, bytesRead, contentLength - bytesRead);
  if(got <= 0) throw new EndOfStreamException(string.Format(
       "Expected {0} bytes; {1} bytes received", contentLength, bytesRead));
  bytesRead += got;
}

基本上,如果流已关闭,每次调用Read 将返回非正数(可能为 0) - 所以您的 while 循环将成为“读取 0,加0,读0,加0,读0,加0,读0,加0”。

最后一点,您的方法建议您根据传入的内容长度标头分配byte[];只是一个警告:确保您对此进行了健全检查并将其限制为合理的值,否则 DOS 攻击是微不足道的。另外,如果可能,我建议尽可能使用流 API,避免一次将其全部加载到内存中(除非您限制了传入的大小,例如这不是问题)。

【讨论】:

  • 感谢您的回答。我测试了您的建议,但 Read 语句永远不会返回零或更小的数字。它总是以正数返回,直到它被阻塞而永远不会返回。
  • @galbarm 建议请求流尚未被发送者关闭,并且尚未发送所有数据;但你说 WireShark 反驳说……它是压缩的(gzip 等)还是在传输过程中加密的?
  • 不,它既不压缩也不加密。我的消息中的典型内容长度约为 5000 字节,当读取被卡住时,bytesRead 值介于 1 到 4999 之间。然而在 Wireshark 中,我看到消息正文的整个字节,与内容长度完全一致(在wireshark的消息和服务器变量中)。
  • @galbarm 那我没有解释;我没有看到这种行为
【解决方案2】:

试试这个实现,你的计数 (contentLength - bytesRead) 是错误的。它应该是缓冲区大小。

byte[] buffer = new byte[bufferSize];
int count;
while ((count = stream.Stream.Read(buffer, 0, buffer.Length)) != 0)
{ 
    // do something with the buffer using count as the end marker
    destination.Write(buffer, 0, count);
}

如果您只想要流中的字节数组,这更像是您正在尝试的:

byte[] buffer = stream.Stream.ToArray()

或者复制到另一个缓冲区:

byte[] data = stream.Stream.ToArray();
Array.CopyTo(data , buffer.Buffer, data.Length)

【讨论】:

  • 我不同意;原件更正确;问题中的代码是 filling 大小的缓冲区(不使用暂存缓冲区在流之间复制,如您的答案所示)。因此,偏移量每次都需要增加(即“已读取的字节数”),“要读取的最大值”需要减小(即“尚未读取的字节数”)。
  • 代码直接来自.Net中的CopyTo流实现,为什么你认为它不起作用?
  • 你没有理解我的意思; OP 没有尝试做 CopyTo。他们正在用预期的数据填充缓冲区;这是非常不同的。
  • 我明白你的意思,同意这取决于 OP 想要做什么。
【解决方案3】:

我只在没有刷新其流的客户端上遇到过类似的错误。 System.IO.Stream.Read 上的 MSDN 文档说:“如果没有数据可用,该实现将阻塞,直到可以读取至少一个字节的数据。”。所以由于某种原因没有可用的数据。我认为您可以设置某个 ReadTimeout 并在相当短的时间后停止等待更多数据。

这里也发布了一个相关问题:C# NetworkStream.Read oddity。也许他的解决方案也能帮到你。

【讨论】:

    【解决方案4】:

    这似乎与我同时使用 NetworkStream(即上面代码中的调用)和通过传递 NetworkStream 对象创建的 StreamReader 的 Read 方法这一事实有关到它的构造函数。

    NetworkStream.Read 用于读取 http 请求消息正文,而 StreamReader.Read 用于读取请求的其余部分(起始行、标头...)。 尽管调用是由同一个线程同步发生的,但这可能是我遇到的行为的原因。

    将代码更改为仅使用 Socket 并直接从套接字执行读取时,它已经解决了问题。

    【讨论】:

      猜你喜欢
      • 2013-02-26
      • 2017-12-29
      • 2011-08-04
      • 2014-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多