我正在实现一个简单的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看看发生了什么:
如您所见,在我的GET请求之后,Google没有响应并且请求会一次又一次地重新传输。我们似乎必须在HTTP请求中指定主机部分。奇怪的部分是我们不。我使用 telnet 发送此请求并得到了Google的回复。我还捕获了telnet发送的请求,它与我的请求完全相同。
我尝试了很多其他网站(例如雅虎,微软),但结果是一样的。
因此,telnet的延迟是否导致Web服务器采取不同的行为(因为在telnet中我们实际上键入字符而不是在1个数据包中将它们一起发送)。
另一个奇怪的问题是,当我将 HTTP / 1.0 更改为 HTTP / 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 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";
在所有类型的请求中,如果我省略主机:部分,则网络服务器不响应,如果使用主机:部分,则即使是无效请求(就像上面的请求一样)将被响应(通过400:HTTP错误请求)。
nos说他的机器上不需要主机:部分,这使情况更加奇怪。
答案 0 :(得分:3)
这适用于使用TcpClient。
我知道这篇文章很老了。我提供此信息以防万一其他人遇到此信息。将此答案视为上述所有答案的补充。
某些服务器需要HTTP主机头,因为它们被设置为每个IP地址承载多个域。作为一般规则,始终发送Host头。一个好的服务器将回复“未找到”。有些服务器根本不会回复。
当从流块中读取数据的调用时,通常是因为服务器正在等待发送更多数据。当没有密切关注HTTP 1.1规范时,通常会出现这种情况。为了证明这一点,请尝试省略最终的CR LF序列,然后从流中读取数据 - 对read的调用将等待,直到客户端超时或服务器通过终止连接放弃等待。
我希望这会有点亮......
答案 1 :(得分:2)
我发现了一个问题:
我如何读取响应并确保我读取HTTP / 1.1请求中的所有响应?
这是我可以回答的问题!
您在这里使用的所有方法都是同步的,易于使用但不太可靠。一旦你得到一个相当大的反应并且只得到它的一部分,你就会看到问题。
要最强大地实现TcpClient连接,您应该使用所有异步方法和回调。相关方法如下:
1)使用调用TcpClient.EndConnect(...)的回调创建与TcpClient.BeginConnect(...)的连接
2)使用TcpClient.GetStream()发送请求。使用回调调用TcpClient.GetStream()的BeginWrite(...)。EndWrite(...)
3)使用TcpClient.GetStream()。BeginRead(...)接收响应,回调调用TcpClient.GetStream()。EndRead(...),将结果追加到StringBuilder缓冲区,然后调用TcpClient.GetStream( ).BeginRead(...)再次(具有相同的回调),直到收到0字节的响应。
这是最后一步(重复调用BeginRead,直到读取0个字节),解决了获取响应,整个响应以及响应的问题。所以帮助我们TCP。
希望有所帮助!
答案 2 :(得分:0)
我建议您尝试使用安装在您自己的本地计算机上的标准的,经过良好测试的,基本上可接受的Web服务器,例如Apache HTTPD或IIS。
将您的网络服务器配置为在没有主机标头的情况下进行响应(例如,IIS中的默认Web应用程序)并查看是否一切顺利。
在底线,您无法真正了解幕后发生的事情,因为您无法控制谷歌,雅虎等网站/网络应用程序。
例如,网站管理员可以使用HTTP协议配置站点,以便端口80上的传入TCP连接没有默认应用程序。
但是,当使用TELNET协议通过TCP端口23连接时,他/她可能想要配置默认的telnet应用程序。
答案 3 :(得分: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);
答案 4 :(得分:-2)
直接尝试使用System.Net.WebClient而不是System.Net.Sockets.TcpClient:
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();
}
}
}