一文串联 HTTP / [ 0.9 | 1.0 | 1.1 | 2 | 3 ]
1989 年,万维网诞生之后,HTTP 迅速成为主导世界的应用层协议。在今天,几乎任何场景都或多或少用到了 HTTP 协议。
在 30 多年的历史中,HTTP 协议本身有比较大的发展,同时,还有一些重大的变动也在酝酿之中。这些演化使得这个协议的表现力更强,性能更好,更能满足日新月异的应用需求。本文就来回顾和展望一下 HTTP 的历史和未来。
HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/3
HTTP/0.9
HTTP/0.9 诞生于 1991 年,是 HTTP 协议的最初版,构造十分简单:
请求端只支持 GET 请求 响应端只能返回 HTML 文本数据
GET /index.html
Hello World
请求示意图如下:
可以看到,HTTP/0.9 只能发送 GET 请求,且每一个请求都单独创建一个 TCP 连接,响应端只能返回 HTML 格式的数据,响应完成之后 TCP 请求断开。
这样的请求方式虽然能够满足当时的使用需求,但也还是暴露出了一些问题。
HTTP/0.9 痛点:
请求方式唯一,返回格式唯一 TCP 连接无法复用
HTTP/1.0
HTTP/1.0 诞生于 1996 年,它在 HTTP/0.9 的基础上,增加了 HTTP 头部字段,极大扩展了 HTTP 的使用场景。这个版本的 HTTP 不仅可以传输文字,还能传输图像、视频、二进制文件,为互联网的迅速发展奠定了坚实的基础。
核心特点如下:
请求端增加 HTTP 协议版本,响应端增加状态码。 请求方法增加 POST、HEAD。 请求端和响应端增加头部字段。 Content-Type 让响应数据不只限于超文本。 Expires、Last-Modified 缓存头。 Authorization 身份认证。 Connection: keep-alive 支持长连接,但非标准。
GET /mypage.html HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)
200 OK
Date: Tue, 15 Nov 1994 08:12:31 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/html
Hello World
请求示意图如下:
可以看到,HTTP/1.0 扩展了请求方法和响应状态码,并且支持定义 HTTP 头部字段,通过 Content-Type
头,我们就能传输任何格式的数据了。同时可以看出,HTTP/1.0 仍然是一个请求对应一个 TCP 连接,不能形成复用。
HTTP/1.0 痛点:
TCP 连接无法复用。 HTTP 队头阻塞,一个 HTTP 请求响应结束之后,才能发起下一个 HTTP 请求。 一台服务器只能提供一个 HTTP 服务。
HTTP/1.1
HTTP/1.1 诞生于 1999 年,它进一步完善了 HTTP 协议,一直用到了 20 多年后的今天,仍然是使用最广的 HTTP 版本。
核心特点如下:
持久连接。
HTTP/1.1 默认开启持久连接,在 TCP 连接建立后不立即关闭,让多个 HTTP 请求得以复用。 管线化技术。
HTTP/1.1 中,多个 HTTP 请求不用排队发送,可以批量发送,这就解决了 HTTP 队头阻塞问题。但批量发送的 HTTP 请求,必须按照发送的顺序返回响应,相当于问题解决了一半,仍然不是最佳体验。 支持响应分块。
HTTP/1.1 实现了流式渲染,响应端可以不用一次返回所有数据,可以将数据拆分成多个模块,产生一块数据,就发送一块数据,这样客户端就可以同步对数据进行处理,减少响应延迟,降低白屏时间。 Bigpipe 的实现就是基于这个特性,具体是通过定义 Transfer-Encoding
头来实现的。增加 Host 头。
HTTP/1.1 实现了虚拟主机技术,将一台服务器分成若干个主机,这样就可以在一台服务器上部署多个网站了。 通过配置 Host 的域名和端口号,即可支持多个 HTTP 服务: Host:
: 其他扩展。
增加 Cache-Control、E-Tag 缓存头。 增加 PUT、PATCH、HEAD、 OPTIONS、DELETE 请求方法。
GET /en-US/docs/Glossary/Simple_header HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/en-US/docs/Glossary/Simple_header
200 OK
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Wed, 20 Jul 2016 10:55:30 GMT
Etag: "547fa7e369ef56031dd3bff2ace9fc0832eb251a"
Keep-Alive: timeout=5, max=1000
Last-Modified: Tue, 19 Jul 2016 00:59:33 GMT
Server: Apache
Transfer-Encoding: chunked
Vary: Cookie, Accept-Encoding
Hello World
请求示意图如下:
可以看到,HTTP/1.1 可以并行发起多个请求,并且也能复用同一个 TCP 连接,传输效率得到了提升。但响应端只能按照发送的顺序进行返回,为此很多浏览器会为每个域名至多打开 6 个连接,用增加队列的方式减少 HTTP 队头阻塞。
HTTP/1.1 痛点:
HTTP 队头阻塞没有彻底解决,响应端必须按照 HTTP 的发送顺序进行返回,如果排序靠前的响应特别耗时,则会阻塞排序靠后的所有响应。
HTTP/2
HTTP/2 诞生于 2015 年,它的最大的特点是 All in 二进制,基于二进制的特性,对 HTTP 传输效率进行了深度优化。
HTTP/2 将一个 HTTP 请求划分为 3 个部分:
帧:一段二进制数据,是 HTTP/2 传输的最小单位。 消息:一个请求或响应对应的一个或多个帧。 数据流:已建立的连接内的双向字节流,可以承载一条或多条消息。
图中可以看到,一个 TCP 连接上有多个数据流,一个数据流承载着双向消息,一条消息包含了多个帧,每个帧都有唯一的标识,指向所在的数据流,来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装,这样就实现了数据传输。
HTTP/2 核心特点如下:
请求优先级 多个 HTTP 请求同时发送时,会产生多个数据流,数据流中有一个优先级的标识,服务器端可以根据这个标识来决定响应的优先顺序。 多路复用 TCP 传输时,不用按照 HTTP 的发送顺序进行响应,可以交错发送,接收端根据帧首部的标识符,就能找到对应的流,进而重新组合得到最终数据。 服务器端推送 HTTP/2 允许服务器未经请求,主动向客户端发送资源,并缓存到客户端中,以避免二次请求。 HTTP/1.1 中请求一个页面时,浏览器会先发送一个 HTTP 请求,然后得到响应的 HTML 内容并开始解析,如果发现有 标签,则会再次发起 HTTP 请求获取对应的 JS 内容。而 HTTP/2 可以在返回 HTML 的同时,将需要用到的 JS、CSS 等内容一并返回给客户端,当浏览器解析到对应标签时,也就不需要再次发起请求了。
头部压缩 HTTP/1.1 的头部字段包含大量信息,而且每次请求都得带上,占用了大量的字节。 HTTP/2.0 中通信双方各自缓存一份头部字段表,如:把 Content-Type:text/html
存入索引表中,后续如果要用到这个头,只需要发送对应的索引号就可以了。
除此之外,虽然 HTTP/2 没有规定必须使用 TLS 安全协议,但所有实现 HTTP/2 的 Web 浏览器都只支持配置过 TLS 的网站,这是为了鼓励大家使用更加安全的 HTTPS。
请求示意图如下:
可以看到,在 HTTP/2 中发送请求时,既不需要排队发送,也不需要排队返回,彻底解决了 HTTP 队头阻塞问题。对于头部信息,资源缓存等痛点也进行了优化,似乎已经是一种很完美的方案了。
HTTP/2 在 HTTP + TCP 的架构上已经优化到了极致,如果要想继续优化,那就只能从这个架构入手了。
首先需要优化的是 TCP,因为 TCP 核心是保证传输层的可靠性,传输效率其实并不好。
TCP 也存在队头阻塞,TCP 在传输时使用序列号标识数据的顺序,一旦某个数据丢失,后面的数据需要等待这个数据重传后才能进行下一步处理。 TCP 每一次建立都需要三次握手,释放连接需要四次挥手,无形中增加了传输时长。 TCP 存在拥塞控制,内置了慢启动,拥塞避免等算法,传输效率并不稳定。
如果要解决这些问题,就需要替换掉 TCP,而这也是 HTTP/3 的解决思路,我们接着往下看。
HTTP/3
HTTP/3 目前还在草案阶段,它的主要特点是对传输层进行了优化,使用 QUIC 替换 TCP,彻底规避了 TCP 传输的效率问题。
QUIC 由 Google 提出的基于 UDP 进行多路复用的传输协议。QUIC 没有连接的概念,不需要三次握手,在应用程序层面,实现了 TCP 的可靠性,TLS 的安全性和 HTTP2 的并发性。在设备支持层面,只需要客户端和服务端的应用程序支持 QUIC 协议即可,无操作系统和中间设备的限制。
HTTP/3 核心特点如下:
传输层连接更快。
HTTP/3 基于 QUIC 协议,可以实现 0-RTT 建立连接,而 TCP 需要 3-RTT 才能建立连接。
传输层多路复用。
上图中的 Stream 之间相互独立,如果 Stream2 丢了一个 Pakcet,不会影响 Stream3 和 Stream4 正常读取。
HTTP/3 传输层使用 QUIC 协议,数据在传输时会被拆分成了多个 packet 包,每一个 packet 包都可以独立、交错发送,不用按顺序发送,也就避免了 TCP 队头阻塞。 改进的拥塞控制。
单调递增的 Packet Number。在 TCP 中,每一个数据包都有一个序列号标识(seq),如果接收端超时没有收到,就会要求重发标识为 seq 的包,如果这时超时的包也接收到了,则无法区分哪个是超时的包,哪个是重传的包。QUIC 中的每一个包的标识(Packet Number)都是单调递增的,重传的 Packet Number 一定大于超时的 Packet Number,这样就能区分开了。 不允许 Reneging。在 TCP 中,如果接收方内存不够或 Buffer 溢出,则可能会把已接收的包丢弃(Reneging),这种行为对数据重传产生了很大的干扰,在 QUIC 中是明确禁止的。在 QUIC 中,一个包只要被确认,就一定是被正确接收了。 更多的 ACK 块。一般来说,接收方收到发送方的消息后都会发送一个 ACK 标识,表示收到了数据。但每收到一个数据就发送一个 ACK 效率太低了,通常是收到多个数据后再统一回复 ACK。TCP 中每收到 3 个数据包就要返回一个 ACK,而 QUIC 最多可以收到 256 个包之后,才返回 ACK。在丢包率比较严重的网络下,更多的 ACK 块可以减少重传量,提升网络效率。 Ack Delay。TCP 计算 RTT 时没有考虑接收方处理数据的延迟,如下图所示,这段延迟即 ACK Delay。QUIC 考虑了这段延迟,使得 RTT 的计算更加准确。 优化的流量控制。
Stream 级别的流量控制中, 接收窗口 = 最大接收窗口- 已接收数据
。Connection 级别的流量控制中, 接收窗口 = Stream1接收窗口 + Stream2接收窗口 + ... + StreamN接收窗口
。TCP 通过滑动窗口来控制流量,如果某一个包丢失了,滑动窗口并不能跨过丢失的包继续滑动,而是会卡在丢失的位置,等待数据重传后,才能继续滑动。 QUIC 流量控制的核心是:不能建立太多的连接,以免响应端处理不过来;不能让某一个连接占用大量的资源,让其他连接没有资源可用。为此 QUIC 流量控制分为 2 个级别:连接级别(Connection Level)和 Stream 级别(Stream Level)。 加密认证的报文
TCP 头部没有经过任何加密和认证,在传输过程中很容易被中间网络设备篡改,注入和窃听。 QUIC 中报文都是经过加密和认证的,在传输过程中保证了数据的安全。 连接迁移
TCP 连接是由(源 IP,源端口,目的 IP,目的端口)组成,这四者中一旦有一项发生改变,这个连接也就不能用了。如果我们从 5G 网络切换到 WiFi 网络,IP 地址就会改变,这个时候 TCP 连接也自然断掉了。 QUIC 使用客户端生成的 64 位 ID 来表示一条连接,只要 ID 不变,这条连接也就一直维持着,不会中断。 前向纠错机制
发送端需要发送三个包,QUIC 在传输时会计算出这三个包的异或值,并单独发出一个校验包,也就是总共发出了四个包。 如果某一个包(非校验包)传输时丢失了,则可以通过另外三个包计算出丢失数据包的内容。 当然这种技术只能用在丢失一个包的情况下,如果丢失了多个包,就只能进行重传了。 QUIC 中发送数据时,除了发送本身的数据包,还会发送验证包,以减少数据丢失导致的重传。 例如:
可以看出,QUIC 丢掉了 TCP 的包袱,基于 UDP,实现了一个安全高效可靠的 HTTP 通信协议。凭借着 0-RTT 建立连接、传输层多路复用、连接迁移、改进的拥塞控制、流量控制等特性,QUIC 在绝大多数场景下获得了比 HTTP/2 更好的效果,HTTP/3 真是未来可期。
思考与总结
本文通过互联网发展历史,从 HTTP/0.9 到 HTTP/3,逐步介绍了每个版本的核心特点,最后再分别一句话总结一下。
HTTP/0.9 实现基本请求响应。 HTTP/1.0 增加 HTTP 头,丰富传输资源类型,奠定互联网发展基础。 HTTP/1.1 增加持久连接、管线化、响应分块,提升了 HTTP 传输效率。 HTTP/2 采用二进制传输格式,通过 HTTP 多路复用、头部压缩、服务器端推送,将传输效率在 HTTP + TCP 架构上发挥到了极致。 HTTP/3 将传输层替换为 QUIC,通过改进的拥塞控制、流量控制、0-RTT 建连、传输层多路复用、连接迁移等特性,进一步提升了 HTTP 传输效率。
可以看到,从 HTTP/1.1 开始,HTTP 的发展方向就是:不断地提升传输效率。期待未来的 HTTP 能够给我们带来更加快速的传输体验。