9

谁能指出这段代码的缺陷?我正在使用 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) 您读取的字节数少于您要求的字节数。

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

4

5 回答 5

10

NetworkStream.Read的文档所暗示的相反,从 a 获得的流TcpClient不会简单地返回 0 来表示没有可用数据时读取的字节数 - 它会阻塞

如果您查看 的文档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);
于 2010-02-03T17:37:56.333 回答
3

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

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

大卫的更新正则表达式

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

内容长度

笔记

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

于 2010-02-03T17:35:30.927 回答
2

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

于 2010-02-03T19:08:29.687 回答
1

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

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

于 2010-02-03T17:36:55.587 回答
0

两个建议...

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

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

    ns.ReadTimeOut=100;
于 2010-02-03T18:01:26.343 回答