HTTP协议基于传输控制协议(Transmission Control Protocol,TCP)。要使用TCP,客户端首先需要打开一个到服务器的连接,才能发送命令。而使用HTTP,发送文本命令。HttpClient和WebListener类隐藏了HTTP协议的细节。使用TCP类发送HTTP请求时,需要更多地了解HTTP协议。TCP类没有提供用于HTTP协议的功能,必须自己提供。另一方面,TCP类提供了更多的灵活性,因为可以使用这些类与基于TCP的其他协议。
传输控制协议(TCP)类为连接和发送两个端点之间的数据提供了简单的方法。端点是IP地址和端口号的组合。已有的协议很好地定义了端口号,例如,HTTP使用端口80,而SMTP使用端口23。Internet地址编码分配机构(Internet Assigned Numbers Authority,IANA,http://www.iana.org)把端口号赋予这些已知的服务。除非实现某个已知的服务,否则应选择大于1024的端口号。
TCP流量构成了目前Internet上的主要流量。TCP通常是首选的协议,因为它提供了有保证的传输、错误校正和缓冲。TcpClient类封装了TCP连接,提供了许多属性来控制连接,包括缓冲、缓冲区的大小和超时。通过GetStream()方法请求NetworkStream对象可以实现读写功能。
TcpListener类用Start()方法侦听引入的TCP连接。当连接请求到达时,可以使用AcceptSocket()方法返回一个套接字,与远程计算机通信,或使用AcceptTcpClient()方法通过高层的TcpClient对象进行通信。阐明TcpListener类和TcpClient类如何协同工作的最简单方式是给出一个示例。
1. 使用TCP创建HTTP客户程序
首先,创建一个控制台应用程序(包),向Web服务器发送一个HTTP请求。以前用HttpClient类实现了这个功能,但使用TcpClient类需要深入HTTP协议。
HttpClientUisngTcp示例代码使用了以下名称空间:
System
System.IO
System.Net.Sockets
System.Text
System.Threading.Tasks
应用程序接受一个命令行参数,传递服务器的名称。这样,就调用RequestHtmlAsync方法,向服务器发出HTTP请求。它用Task的Result属性返回一个字符串:
static async Task Main(string[] args)
{
if (args.Length != 1)
{
ShowUsage();
}
Console.WriteLine(await RequestHtmlAsync(args[0]));
void ShowUsage()
{
Console.WriteLine("Usage: HttpClientUsingTcp hostname");
}
}
现在看看RequestHtmlAsync方法的最重要部分。首先,实例化一个TcpClient对象。其次,使用ConnectAsync方法,在HTTP默认端口80上建立到主机的TCP连接。再次,通过GetStream方法检索一个流,使用这个连接进行读写:
public static async Task<string> RequestHtmlAsync(string url)
{
try
{
Uri uri = new Uri(url);
Console.WriteLine($"hostName: {uri.Host}, port: {uri.Port}, scheme: {uri.Scheme}\n");
using (var client = new TcpClient())
{
await client.ConnectAsync(uri.Host,uri.Port);
using (NetworkStream stream = client.GetStream())
{
...
}
}
}
catch (Exception ex)
{
return ex.Message;
}
}
流现在可以用来把请求写到服务器,读取响应。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 / {uri.Scheme.ToUpper()}/1.1\r\n" +
$"Host: {uri.Host}:{uri.Port}\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);
await ms.WriteAsync(buffer, 0, read);
Array.Clear(buffer, 0, buffer.Length);
} while (read > 0);
ms.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(ms))
{
return await reader.ReadToEndAsync();
}
把一个http协议网站字符串传递给程序,会看到一个成功的请求,其HTML内容显示在控制台上。(即使是http协议,也并非所有网站都请求成功。)
传递网站http://services.odata.org/Northwind/Northwind.svc/Regions,输出结果如下:
hostName: services.odata.org, port: 80, scheme: http
HTTP/1.1 301 Moved Permanently
Content-Type: text/html; charset=UTF-8
Location: https://services.odata.org:80/
Server: Microsoft-IIS/10.0
Date: Thu, 11 Jun 2020 17:19:36 GMT
Connection: close
Content-Length: 0
传递 网站http://yyets.cc/index.php/samples,输出结果如下:
hostName: yyets.cc, port: 80, scheme: http
HTTP/1.1 200 OK
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization, Accept-Encoding, UserAccount
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization, Accept-Encoding, UserAccount
Server: nginx
Date: Thu, 11 Jun 2020 17:40:58 GMT
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
X-Powered-By: PHP/7.0.33
X-Cache: MISS from zz-jp01
X-Cache: MISS from asia-hk30
Transfer-Encoding: chunked
Connection: close
a10
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>YYeTs.cc新人人影视_电影下载网_最新电影_迅雷高清电影下载</title>
<meta name="keywords" content="人人影视,YYeTs,迅雷电影,最新连续剧,好看的高清电影" />
<meta name="description" content="新人人影视-YYeTs.cc是一个免费提供迅雷电影及韩剧,日剧,美剧等电视剧迅雷下载,磁力链,BT种子等下载方式. 在线观看的网站,资源全部来自互联网及网友p2p资源." />
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
<link type="image/vnd.microsoft.icon" href="/favicon.png" rel="shortcut icon" />
<link href="/template/km66/css/app.css" rel="stylesheet" type="text/css" />
<script src="/template/km66/js/jquery.min.js"></script&g