2009-09-09 128 views
7

我正在实现一个简单的HTTP客户端,只是连接到一个Web服务器,并获得其默认主页。这是和它的作品不错:真的很奇怪的HTTP客户端在C#中使用TcpClient

using System; 
using System.Net.Sockets; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      TcpClient tc = new TcpClient(); 
      tc.Connect("www.google.com", 80); 

      using (NetworkStream ns = tc.GetStream()) 
      { 
       System.IO.StreamWriter sw = new System.IO.StreamWriter(ns); 
       System.IO.StreamReader sr = new System.IO.StreamReader(ns); 

       string req = ""; 
       req += "GET/HTTP/1.0\r\n"; 
       req += "Host: www.google.com\r\n"; 
       req += "\r\n"; 

       sw.Write(req); 
       sw.Flush(); 

       Console.WriteLine("[reading...]"); 
       Console.WriteLine(sr.ReadToEnd()); 
      } 
      tc.Close(); 
      Console.WriteLine("[done!]"); 
      Console.ReadKey(); 
     } 
    } 
} 

当我从上面的代码删除下面的线,在sr.ReadToEnd程序块。

req += "Host: www.google.com\r\n"; 

我甚至取代sr.ReadToEndsr.Read,但它无法读取任何东西。我使用Wireshark的,看看有什么发生:

Screenshot of captured packets using Wireshark http://www.imagechicken.com/uploads/1252514718052893500.jpg

正如你看到的,我的GET请求后,谷歌不响应该请求被一次又一次地重发。看来我们必须在HTTP请求中指定主机部分。奇怪的部分是我们不。我用telnet发送这个请求,并得到了谷歌的回应。我还捕获了telnet发送的请求,并且与我的请求完全相同。

我尝试了很多其他网站(例如雅虎,微软),但结果是一样的。

因此,telnet延迟是否会导致web服务器的行为不同(因为在telnet中我们实际上是类型是字符,而不是将它们一起发送到1个数据包中)。


另一个奇怪的问题是当我改变HTTP/1.0HTTP/1.1,程序总是块sr.ReadToEnd线。我想这是因为Web服务器不关闭连接。

的一个解决方案是使用(或的ReadLine)和ns.DataAvailable读取响应。但我无法确定我是否已阅读所有回复。我如何读取响应并确保HTTP/1.1请求的响应中没有剩余字节?


注: 作为W3说,

the Host request-header field MUST accompany all HTTP/1.1 requests

(我这样做是为了我的HTTP/1.1请求)。但我还没有看到这样的事情HTTP/1.0。另外发送请求没有主机头使用telnet工作没有任何问题。


更新:

标志的TCP段被设置为1。我也试过netsh winsock重置重置我的TCP/IP协议栈。测试计算机上没有防火墙和防病毒软件。数据包实际上被发送,因为安装在另一台计算机上的Wireshark可以捕获它。

我也尝试了一些其他的请求。例如,

string req = ""; 
req += "GET/HTTP/1.0\r\n"; 
req += "s df slkjfd sdf/ s/fd \\sdf/\\\\dsfdsf \r\n"; 
req += "qwretyuiopasdfghjkl\r\n"; 
req += "Host: www.google.com\r\n"; 
req += "\r\n"; 

在所有的请求的类型,如果我省略主持人:一部分,Web服务器不响应,如果有主持人:一部分,甚至是无效的请求(只就像上面的请求一样)将被响应(通过400:HTTP Bad Request)。

nos主持人:部分是不需要在他的机器上,这使情况更奇怪。

+0

我不知道这是不是问题,但不应该使用HTTP响应中的内容长度来确定您应该读取多少字节,然后从响应的主体读取那些字节? – Aziz 2009-09-09 17:00:29

+0

@Aziz。也许这是一个很好的解决方案,而不是使用** ReadToEnd **。但在问题的第一部分中,我没有收到来自服务器的任何内容(即使是一个字节)。 – Isaac 2009-09-09 17:09:16

+0

这段代码在有或没有Host:头的情况下工作。 GET请求的TCP段是否设置了PUSH位? - 不是你可以做很多事情,但如果没有设置它可以解释重发 – nos 2009-09-09 18:48:30

回答

0

尝试直接使用,而不是System.Net.Sockets.TcpClient System.Net.WebClient:

using System; 
using System.Net; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      WebClient wc = new WebClient(); 
      Console.WriteLine("[requesting...]"); 
      Console.WriteLine(wc.DownloadString("http://www.google.com")); 
      Console.WriteLine("[done!]"); 
      Console.ReadKey(); 
     } 
    } 
} 
+1

@Remy Lebeau - 谢谢,但我**必须**使用TcpClient,因为我想在较低级别做到这一点。 – Isaac 2009-09-10 00:32:54

+0

@Remy Lebeau - 所以这不是问题的答案,只是分散他人,因为他们认为“他有答案”:/ – Isaac 2009-09-10 07:00:23

+3

@isaac - 如果您必须使用TcpClient,那么您确实需要阅读实际的HTTP规范http://www.ietf.org/rfc/rfc2616.txt。由于ReadToEnd()是处理它们的错误方式,因此您的原始阅读代码在许多情况下都不起作用,就像Aziz先前所说的那样。 – 2009-09-15 22:50:16

2

我发现所有的一个问题:

我怎样才能读取响应,并确定我读取了HTTP/1.1请求中的所有响应?

这就是我可以回答的问题!

您在这里使用的所有方法都是同步的,这很容易使用,但是甚至没有一点可靠。只要你有相当大的回应,你就会看到问题,只会得到它的一部分。

要最有效地实现TcpClient连接,您应该使用所有异步方法和回调。有关方法如下:

1)创建TcpClient.BeginConnect(...)与回调调用TcpClient.EndConnect(...)
2)发送与TcpClient.GetStream请求的连接() (...)使用回调调用TcpClient.GetStream(...).BeginWrite(...)EndWrite(...)
3)用TcpClient.GetStream()。接收回调调用TcpClient.GetStream ().EndRead(...),将结果附加到StringBuilder缓冲区,然后再次调用TcpClient.GetStream()。BeginRead(...)直到收到0字节的响应(具有相同的回调)。

这是最后一步(反复调用BeginRead直到读取0个字节),它解决了获取响应,整个响应以及响应的问题。所以帮助我们TCP。

希望有帮助!

0

我建议你对安装在你自己的本地机器上的标准的,经过严格测试的,广泛接受的Web服务器(如Apache HTTPD或IIS)进行测试。

配置您的Web服务器,以便在没有主机标头(例如IIS中的默认Web应用程序)的情况下进行响应,并查看是否一切顺利。

在底线,你不能真正知道幕后发生了什么,因为你不能控制像谷歌,雅虎等网站/网络应用程序。
例如,网站管理员可以配置站点,以便在端口80上没有使用HTTP协议的传入TCP连接的默认应用程序。
但是他/她可能想要在使用TELNET协议通过TCP端口23连接时配置默认的telnet应用程序。

3

这属于使用TcpClient。

我知道这个帖子是旧的。我提供这些信息是为了防止任何人遇到这种情况。考虑这个答案是所有上述答案的补充。

某些服务器需要HTTP主机标头,因为它们被设置为为每个IP地址托管多个域。作为一般规则,总是发送主机头。一个好的服务器会回复“未找到”。有些服务器根本不会回复。

当从流块中读取数据的调用时,通常是因为服务器正在等待更多要发送的数据。当HTTP 1.1规范未得到严格遵守时,通常就是这种情况。为了演示这一点,尝试省略最终的CR LF序列,然后从流中读取数据 - 读取的调用将等待,直到客户端超时或服务器通过终止连接放弃等待。

我希望这棚一点光......

0

相信ReadToEnd的将等待,直到连接被关闭。但它似乎并没有结束。你应该不断阅读它。然后它会按照您的预期工作。

//Console.WriteLine(sr.ReadToEnd()); 
var bufout = new byte[1024]; 
int readlen=0; 
do 
{ 
    readlen = ns.Read(bufout, 0, bufout.Length); 
    Console.Write(System.Text.Encoding.UTF8.GetString(bufout, 0, readlen)); 
} while (readlen != 0);