【问题标题】:C# NetworkStream.Read oddityC# NetworkStream.Read 奇怪
【发布时间】:2010-02-03 17:17:10
【问题描述】:

谁能指出这段代码的缺陷?我正在使用 TcpClient 检索一些 HTML。与 IIS 服务器通信时,NetworkStream.Read() 似乎永远不会完成。如果我改用 Fiddler 代理,它可以正常工作,但是当直接与目标服务器对话时,.read() 循环不会退出,直到连接异常出现“远程服务器已关闭连接”之类的错误。

internal TcpClient Client { get; set; }

/// bunch of other code here...

try
{

NetworkStream ns = Client.GetStream();
StreamWriter sw = new StreamWriter(ns);

sw.Write(request);
sw.Flush();

byte[] buffer = new byte[1024];

int read=0;

try
{
    while ((read = ns.Read(buffer, 0, buffer.Length)) > 0)
    {
        response.AppendFormat("{0}", Encoding.ASCII.GetString(buffer, 0, read));
    }
}
catch //(SocketException se)
{

}
finally
{
    Close();
}

更新

在调试器中,我可以看到整个响应立即通过并附加到我的 StringBuilder(响应)中。当服务器完成发送响应时,似乎连接没有关闭,或者我的代码没有检测到它。

结论 正如这里所说,最好利用协议的提供(在 HTTP 的情况下,Content-Length 标头)来确定事务何时完成。但是,我发现并非所有页面都设置了内容长度。所以,我现在使用的是混合解决方案:

  1. 对于所有事务,将请求的Connection 标头设置为“关闭”,以阻止服务器保持套接字打开。这提高了服务器在响应您的请求时关闭连接的机会。

  2. 如果设置了Content-Length,则使用它来确定请求何时完成。

  3. 否则,将 NetworkStream 的 RequestTimeout 属性设置为一个较大但合理的值,例如 1 秒。然后,在NetworkStream.Read() 上循环,直到 a) 发生超时,或者 b) 您读取的字节数少于请求的字节数。

感谢大家出色而详细的回复。

【问题讨论】:

  • 我认为你应该分块写请求。这将帮助您调试。
  • 写入工作正常;这是导致问题的读取。
  • 它是否阻塞了Read 调用,或者您是否正在无限执行循环?如果是后者,从流中出来的是什么?你查过response的内容了吗?
  • 是否有任何内容写入 IIS 日志?
  • @Aaronaugh 它正在阻止读取。

标签: c# asp.net tcpclient networkstream


【解决方案1】:

NetworkStream.Read 的文档所暗示的相反,从TcpClient 获得的流只是在没有可用数据时返回0 来表示读取的字节数——它会阻塞。

如果你查看documentation for TcpClient,你会看到这一行:

TcpClient 类提供了在同步阻塞模式下通过网络连接、发送和接收流数据的简单方法。

现在我的猜测是,如果您的Read 调用被阻塞,那是因为服务器已决定不发回任何数据。这可能是因为初始请求没有正常通过。

我的第一个建议是消除 StreamWriter 作为可能的原因(即缓冲/编码细微差别),并使用 NetworkStream.Write 方法直接写入流。如果可行,请确保您为StreamWriter 使用正确的参数。

我的第二个建议是不要依赖Read 调用的结果来打破循环。 NetworkStream 类具有为此设计的 DataAvailable 属性。编写接收循环的正确方法是:

NetworkStream netStream = client.GetStream();
int read = 0;
byte[] buffer = new byte[1024];
StringBuilder response = new StringBuilder();
do
{
    read = netStream.Read(buffer, 0, buffer.Length);
    response.Append(Encoding.ASCII.GetString(buffer, 0, read));
}
while (netStream.DataAvailable);

【讨论】:

  • 再次,请求通过 Fiddler 代理时工作正常。我可以看到整个响应通过并附加到我的 StringBuilder(响应)。当服务器完成发送响应或我的代码没有检测到它时,似乎连接没有关闭。啊。
  • @David:查看我的更新,我添加了一个示例,说明如何使用DataAvailable 编写循环,而不是简单地阻塞每次读取。如果这也失败了,这意味着当你不通过 Fiddler 时,你没有得到服务器的任何响应。
  • @David:请幽默并尝试一下。是的,DataAvailable 表示接收缓冲区中有数据,但Read阻塞调用。您的代码可能是意外工作,因为 Fiddler 过早地关闭了套接字(我之前曾遇到过 Fiddler 的这个问题) - 真正的服务器 没有 有义务立即关闭套接字,实际上不应该总是这样做 - 有时连接需要保持打开状态。你的代码的编写方式,它会总是永远循环,除非它被中断,而且你无法控制那个因素。
  • @David:这正是 HTTP 协议具有 content-length 标头的原因。浏览器所要做的就是读取足够的数据来获取该标头,然后它就知道它还需要读取多少数据。 Chunked 的工作方式不同,但这超出了这个问题的范围。因此,如果您尝试将 TcpClient 与 HTTP 一起使用(为什么不使用 WebRequest 代替?),那么确定的唯一方法是检查内容长度。如果您不知道有多少数据返回,您要么需要依赖DataAvailable,要么等待某个预定的超时时间。
  • @David,没有这样的事情,可以从简单的字节,除非流具有已知长度(NetworkStream 没有)。此逻辑始终是底层协议的一部分。 HTTP 使用content-length 来绕过这个限制,并且较新的 HTTP 1.1 可以使用分块编码,其中每个块都有一个标志,指示是否有更多块。这是一个或另一个。欢迎来到网络编程的美妙世界。 ;)
【解决方案2】:

阅读响应,直到达到双 CRLF。您现在拥有的是响应标头。 解析标头以读取 Content-Length 标头,这将是响应中剩余的字节数。

这是一个可以捕获 Content-Length 标头的正则表达式。

David 的更新正则表达式

Content-Length: (?<1>\d+)\r\n

Content-Length

注意

如果服务器没有正确设置此标头,我将不会使用它。

【讨论】:

【解决方案3】:

不确定这是否有用,但在 HTTP 1.1 中,与服务器的底层连接可能不会关闭,所以流可能也不会关闭?这个想法是您可以重用连接来发送新请求。我认为你必须使用内容长度。或者改用 WebClient 或 WebRequest 类。

【讨论】:

  • 添加“Connection: close”标题解决了这个问题,它似乎适用于几乎所有事情。好电话。
【解决方案4】:

我可能错了,但看起来您对Write 的调用正在(在后台)写入流ns(通过StreamWriter)。稍后,您将从同一个流中读取 (ns)。我不太明白你为什么要这样做?

无论如何,您可能需要在流中使用Seek,才能移动到您要开始阅读的位置。我猜它会在写完之后寻求结束。但正如我所说,我不确定这是否是一个有用的答案!

【讨论】:

  • NetworkStream 就是这样工作的。试图寻找一个总是会抛出一个NotSupportedException
  • Tomas,NetworkStream 绑定到一个缓冲的 IP 通道。写入将数据发送到服务器,读取尝试从接收缓冲区读取。 .Seek() 在这种情况下没有意义。
  • 感谢您的澄清!很高兴你得到了更好的答案!
【解决方案5】:

两个建议...

  1. 您是否尝试过使用 NetworkStream 的 DataAvailable 属性?如果要从流中读取数据,它应该返回 true。

    while (ns.DataAvailable)
    {
     //Do stuff here
    }
  1. 另一种选择是将 ReadTimeOut 更改为较低的值,这样您就不会长时间阻塞。可以这样做:

    ns.ReadTimeOut=100;

【讨论】:

  • 我担心当目标 IIS 服务器负载过重时,这可能会导致我过早关闭套接字。我认为DataAvailable表示接收缓冲区中有数据;如果为假,服务器可能仍在渲染要发送的数据。设置低超时可能会导致同样的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-12-24
  • 1970-01-01
  • 2021-12-03
  • 2011-06-14
  • 2015-05-07
  • 2013-08-16
  • 1970-01-01
相关资源
最近更新 更多