我正在实现一个简单的HTTP客户端,它只是连接到Web服务器并获取其默认主页。这是它的代码,它可以很好地工作:
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.Read 替换了 sr.ReadToEnd,但仍然无法读取任何内容。我使用 Wireshark 查看发生了什么: 使用 Wireshark 捕获数据包的屏幕截图 http://www.imagechicken.com/uploads/1252514718052893500.jpg 如您所见,在我的 GET 请求之后,Google 没有响应,请求一次又一次地重新发送。看来我们必须在 HTTP 请求中指定Host 部分。奇怪的是我们不必这样做。我使用 telnet 发送了这个请求并从 Google 得到了响应。我还捕获了由 telnet 发送的请求,它和我的请求完全相同。
我尝试了许多其他网站(例如 Yahoo、Microsoft),但结果都是一样的。
那么,telnet 中的延迟是否会导致 Web 服务器有所不同(因为在 telnet 中我们实际上是一个一个字符地输入,而不是将它们一起发送在一个数据包中)?
另一个奇怪的问题是,当我将 HTTP/1.0 改为 HTTP/1.1 时,程序总是在 sr.ReadToEnd 行上阻塞。我猜这是因为 Web 服务器没有关闭连接。
一种解决方案是使用 Read(或 ReadLine)和 ns.DataAvailable 读取响应。但我无法确定是否已经读取了所有响应内容。如何读取响应并确保 HTTP/1.1 请求中没有剩余的字节?
注意:如W3所述,
(我在我的 HTTP/1.1 请求中已经这样做了)。但是我没有看到类似的要求适用于HTTP/1.0。此外,使用 telnet 发送没有Host头的请求也可以正常工作。
更新:
TCP段中的Push标志已设置为1。我还尝试了netsh winsock reset来重置我的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";
在所有类型的请求中,如果我省略了 Host: 部分,Web 服务器不会响应,而如果有了 Host: 部分,即使是无效的请求(就像上面的请求一样),也会得到响应(通过 400:HTTP 错误请求)。 nos 表示在他的机器上不需要 Host: 部分,这让情况更加奇怪。