写在前面
“超文本传输协议” HTTP,或称“HTTP over TCP/IP”(HTTPS 为“HTTP over SSL/TLS”),用于约定和规范在两点之间的数据传输。其中“超文本”体现在 HTTP 传输的是文字、图片、音视频、超链接等数据,而不是难以阅读甚至会被切分的二进制包等形式。
但作为应用层的协议 HTTP 并不负责“传输”,而是由下层的协议来协助完成传输的工作。如 DNS 协议实现域名解析、SSL/TLS 协议实现安全通信、IP 协议实现寻址和路由、TCP 协议实现可靠数据传输等等。
本文记录一次简单实验,搭建最小化的由请求方和应答方两个端点构成的环境,以了解 HTTP 的数据传输过程以及 TCP/IP 在其中扮演的角色。
环境准备
Chrome + NodeJS Server + Wireshark
实验使用 Chrome 浏览器作为请求方,使用 NodeJS 创建 HTTP 服务模拟 Web 服务器,使用 Wireshark 抓包工具截获 TCP/IP 协议栈中传输的码流以作抓包分析。
一般 HTTP 服务器由 Aphche、Nginx 之类的软件来搭建,但 NodeJS 本身提供的http模块就可以用来构建服务器。以下是使用 http 模块创建的简单服务:
/* hello_http.js */
// 引入 http 模块
const http = require("http");
// 创建服务
http.createServer(function (req, res) {
if (req.url === '/hello') {
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
res.write('<h1 style="text-align:center">Hello HTTP</h1>');
} else {
res.writeHead(404, {
"Content-Type": "text/html;charset=UTF-8"
})
}
res.end()
}).listen(3000);
程序启动后将监听本地 3000 端口,访问/hellourl 时将响应 200 状态码及HTML文本内容。
实验过程
1、启动 NodeJS Server 脚本:在控制台输入 node hello_http.js
2、Wireshark首页,选择 “loopback lo0”,然后在顶部的过滤器中输入 tcp.port == 3000
其中,客户端和服务器在同一台机器而没有与实际设备连接, “loopback lo0”作为回环网卡并不真实地从外界接收和发送数据包,lo 是 local 的简写。
3、 使用浏览器访问http://127.0.0.1:3000/hello
从浏览器发起请求到传输完毕,Wireshark 在此过程中截获的内容:
抓包分析
传输过程
TCP 68 57602 → 3000 [SYN] Seq=0 Win=65535
TCP 68 3000 → 57602 [SYN, ACK] Seq=0 Ack=1 Win=65535
TCP 56 57602 → 3000 [ACK] Seq=1 Ack=1 Win=408256
TCP 56 [TCP Window Update] 3000 → 57602 [ACK] Seq=1 Ack=1 Win=408256
HTTP 546 GET /hello HTTP/1.1
TCP 56 3000 → 57602 [ACK] Seq=1 Ack=491 Win=407808
HTTP 259 HTTP/1.1 200 OK (text/html)
TCP 56 57602 → 3000 [ACK] Seq=491 Ack=204 Win=408064
-
HTTP 协议运行在 TCP/IP 基础上,浏览器需先依照 TCP 协议的规范,使用 三次握手 与 Web 服务器建立连接:
- [SYN]:客户端发送 SYN(Seq=x)给服务器,进入 SYN_SEND 状态,等待服务器确认
- [SYN, ACK]:服务器收到 SYN 包,需向客户端进行确认(Ack=x+1),同时发送一个自己的SYN包(Seq=y),进入 SYN_RECV 状态
- [ACK]:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK (Ack=y+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手
-
服务器端向浏览器发送 [TCP Window Update] ,表示buffer已经清空,并提示服务端现在已有足够的 window 大小
-
连接通道建立后,浏览器按照 HTTP 协议规定的格式,通过 TCP 发送了一个请求报文
GET /hello HTTP/1.1 -
服务器端回应 [ACK],表示收到请求报文
-
而后服务器端通过 TCP 发送 HTTP 响应报文
HTTP/1.1 200 OK -
浏览器也回应 [ACK] 表示收到响应报文
tcp window update是 TCP 通信中的一个状态,发生的原因归结于发送者传输数据的速度比接收者读取的数据还快,这使得接受端在缓冲区必须释放一部分空间来装发送过来的数据,然后向发送者发送 Windows Update,告诉发送者应以多大的速度发送数据,从而使得数据传输与接受恢复正常。
HTTP 报文
TCP 协议的报文对于 HTTP 协议是不可见的,使用 Wireshark 跟踪码流时,得到以下 HTTP 报文:
HTTP 协议的报文为可读的 ASCII 码,请求报文和响应报文的结构基本相同,由三部分组成:
- 起始行(start line),描述请求或响应的基本信息
- 请求行,请求报文里的起始行,包含 请求方法 Method + 请求目标 URI + HTTP 协议版本:
GET /hello HTTP/1.1 - 状态行,响应报文里的起始行,包含 HTTP 协议版本 + 状态码 + 原因:
HTTP/1.1 200 OK
- 请求行,请求报文里的起始行,包含 请求方法 Method + 请求目标 URI + HTTP 协议版本:
- 头部字段集合(header),使用 key-value 形式更详细地说明报文,使用
\r\n即二进制的“0D0A”进行分隔 - 消息正文(entity/body),实际传输的数据,可以是文本或图片、视频等二进制数据
header 与 body 之间使用空白行(\r\n)进行分隔,若没有 body,则报文以空白行作为结束。
另外,由于 HTTP/1.1 长连接特性,默认不会立即关闭 TCP 连接,所以没有截获 TCP 关闭连接 四次挥手 的过程。
小结
HTTP 协议基于底层的 TCP/IP 协议,所以客户端需要用 IP 地址与服务器建立 TCP 连接。
本实验中,浏览器从地址栏的输入中就可获得服务器的 IP 地址和端口号。若输入的是域名,浏览器还需访问缓存或一系列的域名解析服务器以得到目标的IP地址。
在现实的 HTTP 传输过程中,浏览器和服务器之间还有一些“中间人”的角色,如 CDN、网关、代理等,可以帮助用户更快速、更安全地获取资源。