简体   繁体   English

C#NetworkStream.Read奇怪

[英]C# NetworkStream.Read oddity

Can anyone point out the flaw in this code? 任何人都能指出这段代码中的缺陷吗? I'm retrieving some HTML with TcpClient. 我正在使用TcpClient检索一些HTML。 NetworkStream.Read() never seems to finish when talking to an IIS server. 与IIS服务器通信时,NetworkStream.Read()似乎永远不会完成。 If I go use the Fiddler proxy instead, it works fine, but when talking directly to the target server the .read() loop won't exit until the connection exceptions out with an error like "the remote server has closed the connection". 如果我去使用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();
}

Update 更新

In the debugger, I can see the entire response coming through immediately and being appended to my StringBuilder (response). 在调试器中, 我可以看到整个响应立即通过并附加到我的StringBuilder(响应)。 It just appears that the connection isn't being closed when the server is done sending the response, or my code isn't detecting it. 当服务器完成发送响应或我的代码没有检测到它时,似乎连接没有被关闭。

Conclusion As has been said here, it's best to take advantage of the offerings of the protocol (in the case of HTTP, the Content-Length header) to determine when a transaction is complete. 结论正如这里所说的,最好利用协议的产品(在HTTP的情况下,Content-Length头)来确定事务何时完成。 However, I've found that not all pages have content-length set. 但是,我发现并非所有页面都设置了内容长度。 So, I'm now using a hybrid solution: 所以,我现在正在使用混合解决方案:

  1. For ALL transactions, set the request's Connection header to "close", so that the server is discouraged from keeping the socket open. 对于所有事务,请将请求的Connection标头设置为“close”,以防止服务器保持套接字打开。 This improves the chances that the server will close the connection when it is through responding to your request. 这可以提高服务器在响应您的请求时关闭连接的几率。

  2. If Content-Length is set, use it to determine when a request is complete. 如果设置了Content-Length ,则使用它来确定请求何时完成。

  3. Else, set the NetworkStream's RequestTimeout property to a large, but reasonable, value like 1 second. 否则,将NetworkStream的RequestTimeout属性设置为一个大但合理的值,如1秒。 Then, loop on NetworkStream.Read() until either a) the timeout occurs, or b) you read fewer bytes than you asked for. 然后,在NetworkStream.Read()上循环,直到a)发生超时,或者b)读取的字节数比您要求的少。

Thanks to everyone for their excellent and detailed responses. 感谢大家的出色和详细的回复。

Contrary to what the documentation for NetworkStream.Read implies, the stream obtained from a TcpClient does not simply return 0 for the number of bytes read when there is no data available - it blocks. NetworkStream.Read的文档所暗示的相反,从没有数据可用时,从TcpClient获取的流不会返回0表示读取的字节数 - 它会阻塞。

If you look at the documentation for TcpClient , you will see this line: 如果查看TcpClient文档 ,您将看到以下行:

The TcpClient class provides simple methods for connecting, sending, and receiving stream data over a network in synchronous blocking mode . TcpClient类提供了在同步阻塞模式下通过网络连接,发送和接收流数据的简单方法。

Now my guess is that if your Read call is blocking, it's because the server has decided not to send any data back. 现在我的猜测是,如果你的Read调用是阻塞的,那是因为服务器决定不再发送任何数据。 This is probably because the initial request is not getting through properly. 这可能是因为初始请求没有正确完成。

My first suggestion would be to eliminate the StreamWriter as a possible cause (ie buffering/encoding nuances), and write directly to the stream using the NetworkStream.Write method. 我的第一个建议是消除StreamWriter作为可能的原因(即缓冲/编码细微差别),并使用NetworkStream.Write方法直接写入流。 If that works, make sure that you're using the correct parameters for the StreamWriter . 如果可行,请确保您使用StreamWriter的正确参数。

My second suggestion would be not to depend on the result of a Read call to break the loop. 我的第二个建议是不依赖于Read调用的结果来打破循环。 The NetworkStream class has a DataAvailable property that is designed for this. NetworkStream类具有DataAvailable设计的DataAvailable属性。 The correct way to write a receive loop is: 编写接收循环的正确方法是:

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);

Read the response until you reach a double CRLF. 阅读回复,直到达到双重CRLF。 What you now have is the Response headers. 你现在拥有的是Response标头。 Parse the headers to read the Content-Length header which will be the count of bytes left in the response. 解析标头以读取Content-Length标头,该标头将是响应中剩余的字节数。

Here is a regular expression that can catch the Content-Length header. 这是一个可以捕获Content-Length标头的正则表达式。

David's Updated Regex 大卫的更新正则表达式

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

Content-Length 内容长度

Note 注意

If the server does not properly set this header I would not use it. 如果服务器没有正确设置此标头,我将不会使用它。

Not sure if this is helpful or not but with HTTP 1.1 the underlying connection to the server might not be closed so maybe the stream doesn't get closed either? 不确定这是否有用,但是对于HTTP 1.1,服务器的底层连接可能没有关闭,所以流也可能没有关闭? The idea being that you can reuse the connection to send a new request. 我们的想法是您可以重用连接来发送新请求。 I think you have to use the content-length. 我认为你必须使用内容长度。 Alternatively use the WebClient or WebRequest classes instead. 或者,也可以使用WebClient或WebRequest类。

I may be wrong, but it looks like your call to Write is writing (under the hood) to the stream ns (via StreamWriter ). 我可能错了,但看起来你对Write的调用是在(通过引擎盖)写入流ns (通过StreamWriter )。 Later, you're reading from the same stream ( ns ). 之后,您正在读取相同的流( ns )。 I don't quite understand why are you doing this? 我不太明白你为什么要这样做?

Anyway, you may need to use Seek on the stream, to move to the location where you want to start reading. 无论如何,您可能需要在流上使用Seek ,移动到您想要开始阅读的位置。 I'd guess that it seeks to the end after writing. 我猜它写完后会寻求结束。 But as I said, I'm not really sure if this is a useful answer! 但正如我所说,我不确定这是否是一个有用的答案!

Two Suggestions... 两个建议......

  1. Have you tried using the DataAvailable property of NetworkStream? 您是否尝试过使用NetworkStream的DataAvailable属性? It should return true if there is data to be read from the stream. 如果要从流中读取数据,它应该返回true。

    while (ns.DataAvailable)
    {
     //Do stuff here
    }
  1. Another option would be to change the ReadTimeOut to a low value so you don't end up blocking for a long time. 另一个选择是将ReadTimeOut更改为较低的值,这样您就不会长时间阻塞。 It can be done like this: 它可以这样做:

    ns.ReadTimeOut=100;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM