C#使用TCP创建HTTP客户程序

首先,创建一个控制台应用程序(包),向 Web 服务器发送一个 HTTP 请求。以前用 HttpClient 类实现了这个功能,但使用 TcpClient 类需要深入 HTTP 协议。

HttpClientUsingTcp 示例代码使用了以下名称空间:.

System 
System.IO
System.Net.Sockets 
System.Text
System.Threading.Tasks

应用程序接受一个命令行参数,传递服务器的名称。这样,就调用RequestHtmlAsync 方法,向服务器发出 HTTP 请求。它用 Task 的 Result 属性返回一个字符串:

static void Main(string[] args)
{
  if (args.Length != 1)
  {
    ShowUsage();
  }
  Task<string> tl = RequestHtmlAsync(args[0]); 
  Console.WriteLine(t1.Result); 
  Console.ReadLine();
}

private static void ShowUsage()
{
  Console.WriteLine("Usage: HttpClientUsingTcp hostname");
}

现在看看 RequestHtmlAsync 方法的最重要部分。首先,实例化一个 TcpClient 对象。其次,使用 ConnectAsync 方法,在 HTTP 默认端口 80 上建立到主机的TCP 连接。再次,通过 GetStream 方法检索一个流,使用这个连接进行读写:

private const int ReadBufferSize = 1024;
public static async Task<string> RequestHtmlAsync(string hostname)
{
  try
  {
    using (var client = new TcpClient())
    {
      await client.ConnectAsync(hostname, 80); 
      NetworkStream stream = client.GetStream();
      //...
    }
  }
}

流现在可以用来把请求写到服务器,读取响应。HTTP 是一种基于文本的协议,所以很容易在字符串中定义请求。为了向服务器发出一个简单的请求,标题定义了 HTTP 方法 GET,其后是 URL/ 的路径和 HTTP 版本 HTTP/1.1。第二行定义了 Host 标题、主机名和端口号,第三行定义了 Connection 标题。通常,通过Connection 标题,客户端请求 keep-alive,要求服务器保持连接打开,因为客户端希望发出更多的请求。这里只向服务器发出一个请求,所以服务器应该关闭连接,从而 close 设置为 Connection 标题。为了结束标题信息,需要使用 \r\n 给请求添加一个空行。标题信息调用 NetworkStream 的方法 WriteAsync,用UTF-8 编码发送。\r\n 为了立即向服务器发送缓存,请调用 FlushAsync 方法。否则数据就可能保存在本地缓存:

//...
string header = "GET/HTTP/1.1\r\n" +
$"Host: {hostname}:80\r\n" +
"Connection: close\r\n" +
"\r\n";
byte[] buffer = Encoding.UTF8.GetBytes(header); 
await stream.WriteAsync(buffer, 0, buffer.Length); 
await stream.FlushAsync();

现在可以继续这个过程,从服务器中读取回应。不知道回应有多大,所以创建一个动态生长的 MemoryStream。使用 ReadAsync 方法把服务器的回应暂时写入一个字节数组,这个字节数组的内容添加到 MemoryStream 中。从服务器中读取所有数据后,StreamReader 接管控制,把数据从流读入一个字符串,并返回给调用者:

var ms = new MemoryStream();
buffer = new byte[ReadBufferSize]; 
int read = 0; 
do
{
  read = await stream.ReadAsync(buffer, 0, ReadBufferSize); 
  ms.Write(buffer, 0, read);
  Array.Clear(buffer, 0, buffer.Length);
  } while (read > 0);
  ms.Seek(0, SeekOrigin.Begin);
  var reader = new StreamReader(ms); 
  return reader.ReadToEnd();
}
//...

把一个网站传递给程序,会看到一个成功的请求,其 HTML 内容显示在控制台上。现在该创建一个 TCP 侦听器和自定义协议了。