HTTP (无状态、简单可扩展)是浏览器中最重要且使用最多的协议,是一种允许浏览器向服务器获取资源的协议,是浏览器和服务器之间的通信语言。
即将完成使命的HTTP/1
超文本传输协议HTTP/0.9:
- 请求GET/mypage.html
- 响应只有HTML文档
被浏览器推动的HTTP/1.0:
新特性:
①引入了请求头和响应头,以Key-Value的形式保存。
请求头:
期望服务器返回的文件的类型
表示期望服务器采用的文件压缩方式
期望服务器返回的文件的编码方式
期望页面的优先语言
响应头:
服务器采用的压缩方法
服务器返回的文件类型
服务器返回的文件的编码方式
②引入了状态码
通过响应行的方式来通知浏览器
③引入了Cache机制
缓存已经下载过的数据,主要使用 header 里的 If-Modified-Since、Expires 来做为缓存判断的标准
④请求代理
服务器需要统计客户端的基础信息,比如 Windows 和 macOS 的用户数量分别是多少,所以 HTTP/1.0 的请求头中还加入了用户代理的字段。
缝缝补补的HTTP/1.1
新特性:
①增加了持久连接
HTTP/1.0 每进行一次 HTTP 通信,都需要经历建立 TCP 连接、传输 HTTP 数据和断开 TCP 连接三个阶段。开销很大。HTTP/1.1 中增加了持久连接的方法,它的特点是在一个 TCP 连接上可以传输多个 HTTP 请求,只要浏览器或者服务器没有明确断开连接,那么该 TCP 连接会一直保持,减少了服务器额外的负担。
持久连接在 HTTP/1.1 中是默认开启的,所以你不需要专门为了持久连接去 HTTP 请求头设置信息,如果要关闭持久连接,可以在 HTTP 请求头中加上Connection: close。引入CDN,并且浏览器对于同一个域名,默认允许同时建立 6 个 TCP 持久连接。
②不成熟的管线化
队头阻塞问题:持久连接虽然能减少 TCP 的建立和断开次数,但是它需要等待前面的请求返回之后,才能进行下一次请求。如果 TCP 通道中的某个请求因为某些原因没有及时返回,那么就会阻塞后面的所有请求。
HTTP/1.1试图通过管线化去解决队头阻塞问题,但这个提议已被废弃,所以队头阻塞问题HTTP/1.1并未解决。
③提供虚拟主机的支持
在 HTTP/1.0 中,每个域名绑定了一个唯一的 IP 地址,因此一个服务器只能支持一个域名。但是随着虚拟主机技术的发展,需要实现在一台物理主机上绑定多个虚拟主机,每个虚拟主机都有自己的单独的域名,这些单独的域名都公用同一个 IP 地址。因此,HTTP/1.1 的请求头中增加了 Host 字段,用来表示当前的域名地址,这样服务器就可以根据不同的 Host 值做不同的处理。
④对动态生成的内容提供了支持
在设计 HTTP/1.0 时,需要在响应头中设置完整的数据大小,如Content-Length: 901,这样浏览器就可以根据设置的数据大小来接收数据。不过随着服务器端的技术发展,很多页面的内容都是动态生成的,因此在传输数据之前并不知道最终的数据大小,这就导致了浏览器不知道何时会接收完所有的文件数据。HTTP/1.1 通过引入 Chunk transfer 机制来解决这个问题,服务器会将数据分割成若干个任意大小的数据块,每个数据块发送时会附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志。这样就提供了对动态内容的支持。
⑤客户端Cookie,安全机制
HTTP/1.1仍然存在的问题:
对于带宽的利用率不理想
带宽是指每秒最大能发送或者接收的字节数。我们把每秒能发送的最大字节数称为上行带宽,每秒能够接收的最大字节数称为下行带宽。
对带宽利用率不理想的原因:
原因一:TCP的慢启动。(由TCP本身机制导致。慢启动是 TCP 为了减少网络拥塞的一种策略,我们是没有办法改变的)
一旦一个 TCP 连接建立之后,就进入了发送数l据状态,刚开始 TCP 协议会采用一个非常慢的速度去发送数据,然后慢慢加快发送数据的速度,直到发送数据的速度达到一个理想状态,我们把这个过程称为慢启动。
之所以说慢启动会带来性能问题,是因为页面中常用的一些关键资源文件本来就不大,如 HTML 文件、CSS 文件和 JavaScript 文件,通常这些文件在 TCP 连接建立好之后就要发起请求的,但这个过程是慢启动,所以耗费的时间比正常的时间要多很多,这样就推迟了宝贵的首次渲染页面的时长了。
原因二:同时开启了多条TCP连接,这些连接会竞争固定的带宽。(由TCP本身机制导致)
有的 TCP 连接下载的是一些关键资源,如 CSS 文件、JavaScript 文件等,而有的 TCP 连接下载的是图片、视频等普通的资源文件,但是多条 TCP 连接之间又不能协商让哪些关键资源优先下载,这样就有可能影响那些关键资源的下载速度了。
原因三:HTTP/1.1队头阻塞的问题。多个请求公用一个 TCP 管道,在一个管道中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态,因此数据不能并行请求。(由HTTP/1.1机制导致)
正向我们走来的HTTP/2——提升网络速度
虽然 TCP 有问题,但是我们依然没有换掉 TCP 的能力,所以我们就要想办法去规避 TCP 的慢启动和 TCP 连接之间的竞争问题。
基于此,HTTP/2 的思路就是一个域名只使用一个 TCP 长连接来传输数据,这样整个页面资源的下载过程只需要一次慢启动,同时也避免了多个 TCP 连接竞争带宽所带来的问题。
另外,就是队头阻塞的问题,等待请求完成后才能去请求下一个资源,这种方式无疑是最慢的,所以 HTTP/2 需要实现资源的并行请求,也就是任何时候都可以将请求发送给服务器,而并不需要等待其他请求的完成,然后服务器也可以随时返回处理好的请求资源给浏览器。
所以,HTTP/2 的解决方案可以总结为:一个域名只使用一个 TCP 长连接和消除应用层的队头阻塞问题。
HTTP/2最核心的多路复用机制(通过一个TCP连接来发送多个URL请求),实现资源的并行传输:
从图中你会发现每个请求都有一个对应的 ID,如 stream1 表示 index.html 的请求,stream2 表示 foo.css 的请求。这样在浏览器端,就可以随时将请求发送给服务器了。服务器端接收到这些请求后,会根据自己的喜好来决定优先返回哪些内容,比如服务器可能早就缓存好了 index.html 和 bar.js 的响应头信息,那么当接收到请求的时候就可以立即把 index.html 和 bar.js 的响应头信息返回给浏览器,然后再将 index.html 和 bar.js 的响应体数据返回给浏览器。之所以可以随意发送,是因为每份数据都有对应的 ID,浏览器接收到之后,会筛选出相同 ID 的内容,将其拼接为完整的 HTTP 响应数据。HTTP/2 使用了多路复用技术,可以将请求分成一帧一帧的数据去传输,这样带来了一个额外的好处,就是当收到一个优先级高的请求时,比如接收到 JavaScript 或者 CSS 关键资源的请求,服务器可以暂停之前的请求来优先处理关键资源的请求。
为了解决HTTP/1.1存在的问题,HTTP/2采用了多路复用机制,具体实现是在协议栈中添加了二进制分帧层:
- 首先,浏览器准备好请求数据,包括了请求行、请求头等信息,如果是 POST 方法,那么还要有请求体。
- 这些数据经过二进制分帧层处理之后,会被转换为一个个带有请求 ID 编号的帧,通过协议栈将这些帧发送给服务器。
- 服务器接收到所有帧之后,会将所有相同 ID 的帧合并为一条完整的请求信息。
- 然后服务器处理该条请求,并将处理的响应行、响应头和响应体分别发送至二进制分帧层。
- 同样,二进制分帧层会将这些响应数据转换为一个个带有请求 ID 编号的帧,经过协议栈发送给浏览器。
- 浏览器接收到响应帧之后,会根据 ID 编号将帧的数据提交给对应的请求。
HTTP发生改变的只是传输方式,多路复用机制建立在二进制分帧层基础之上,基于二进制分帧层,HTTP/2 还附带实现了很多其他功能:
HTTP/2其他特性:
- 可以设置请求的优先级
- 服务器将数据提前推送到浏览器 (比如先发送的HTML文件中引用了js和CSS文件,浏览器未请求服务器就把这些信息推送给浏览器)
- 对请求头和响应头进行了压缩
虽然 HTTP/2 解决了 HTTP/1.1 中的队头阻塞问题,但是 HTTP/2 依然是基于 TCP 协议的,而 TCP 协议依然存在数据包级别的队头阻塞问题,TCP 的队头阻塞是如何影响到 HTTP/2 性能的呢?
在tcp层 Tls层以上的数据都是tcp层的数据,tcp层对每个数据包都有编号,分为1,2,3 … tcp保证双向稳定可靠的传输,如果2包数据丢失,1号包和3号包来了,那么在超时重传时间还没有收到2编号数据包,服务端会发送2号数据包,客服端收到之后,发出确认,服务端才会继续发送其他数据,客户端数据才会呈现给上层应用层,这样tcp层的阻塞就发生了。(TCP传输过程中也是把一份数据分为多个数据包的。当其中一个数据包没有按照顺序返回,接收端会一直保持连接等待数据包返回,这时候就会阻塞后续请求。)
未来的HTTP/3——甩掉TCP、TLS的包袱
在 HTTP/1.1 时代,为了提升并行下载效率,浏览器为每个域名维护了 6 个 TCP 连接;而采用 HTTP/2 之后,浏览器只需要为每个域名维护 1 个 TCP 持久连接,同时还解决了 HTTP/1.1 队头阻塞的问题。
但是HTTP/2还是存在一些问题:
①TCP 的队头阻塞
虽然 HTTP/2 解决了应用层面的队头阻塞问题,不过和 HTTP/1.1 一样,HTTP/2 依然是基于 TCP 协议的,而 TCP 最初就是为了单连接而设计的。你可以把 TCP 连接看成是两台计算机之前的一个虚拟管道,计算机的一端将要传输的数据按照顺序放入管道,最终数据会以相同的顺序出现在管道的另外一头。
HTTP/1.1 协议栈中 TCP 是如何传输数据的?
从一端发送给另外一端的数据会被拆分为一个个按照顺序排列的数据包,这些数据包通过网络传输到了接收端,接收端再按照顺序将这些数据包组合成原始数据,这样就完成了数据传输。
不过,如果在数据传输的过程中,有一个数据因为网络故障或者其他原因而丢包了,那么整个 TCP 的连接就会处于暂停状态,需要等待丢失的数据包被重新传输过来。你可以把 TCP 连接看成是一个按照顺序传输数据的管道,管道中的任意一个数据丢失了,那之后的数据都需要等待该数据的重新传输。
把在 TCP 传输过程中,由于单个数据包的丢失而造成的阻塞称为 TCP 上的队头阻塞。
队头阻塞是怎么影响 HTTP/2 传输的呢?
在 HTTP/2 中,多个请求是跑在一个 TCP 管道中的,如果其中任意一路数据流中出现了丢包的情况,那么就会阻塞该 TCP 连接中的所有请求。这不同于 HTTP/1.1,使用 HTTP/1.1 时,浏览器为每个域名开启了 6 个 TCP 连接,如果其中的 1 个 TCP 连接发生了队头阻塞,那么其他的 5 个连接依然可以继续传输数据。所以随着丢包率的增加,HTTP/2 的传输效率也会越来越差。有测试数据表明,当系统达到了 2% 的丢包率时,HTTP/1.1 的传输效率反而比 HTTP/2 表现得更好。
②TCP 建立连接的延时
除了 TCP 队头阻塞之外,TCP 的握手过程也是影响传输效率的一个重要因素。
为了搞清楚 TCP 协议建立连接的延迟问题,我们还是先来回顾下网络延迟的概念,这会有助于你对后面内容的理解。网络延迟又称为 RTT(Round Trip Time)。我们把从浏览器发送一个数据包到服务器,再从服务器返回数据包到浏览器的整个往返时间称为 RTT。RTT 是反映网络性能的一个重要指标。
那建立 TCP 连接时,需要花费多少个 RTT 呢?
下面我们来计算下。我们知道 HTTP/1 和 HTTP/2 都是使用 TCP 协议来传输的,而如果使用 HTTPS 的话,还需要使用 TLS 协议进行安全传输,而使用 TLS 也需要一个握手过程,这样就需要有两个握手延迟过程。
1,在建立 TCP 连接的时候,需要和服务器进行三次握手来确认连接成功,也就是说需要在消耗完 1.5 个 RTT 之后才能进行数据传输。
2,进行 TLS 连接,TLS 有两个版本——TLS1.2 和 TLS1.3,每个版本建立连接所花的时间不同,大致是需要 1~2 个 RTT,关于 HTTPS 我们到后面到安全模块再做详细介绍。
总之,在传输数据之前,我们需要花掉 3~4 个 RTT。
③TCP协议僵化
HTTP/3 选择了一个折衷的方法——UDP 协议,基于 UDP 实现了类似于 TCP 的多路数据流、传输可靠性等功能,我们把这套功能称为 QUIC 协议(QUIC 看成是集成了“TCP+HTTP/2 的多路复用 +TLS 等功能”的一套协议)。关于 HTTP/2 和 HTTP/3 协议栈的比较,你可以参考下图:
- 实现了类似 TCP 的流量控制、传输可靠性的功能。虽然 UDP 不提供可靠性的传输,但 QUIC 在 UDP 的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些 TCP 中存在的特性
- 集成了 TLS加密功能,减少了握手所花费的 RTT 个数。
- 实现了 HTTP/2 中的多路复用功能。QUIC 实现了在同一物理连接上可以有多个独立的逻辑数据流。实现了数据流的单独传输,就解决了 TCP中队头阻塞的问题。
- 实现了快速握手功能。由于 QUIC 是基于 UDP 的, 可以实现使用 0-RTT 或者 1-RTT 来建立连接。
HTTP/3面临的挑战:
- 服务器和浏览器端都没有对 HTTP/3 提供比较完整的支持。
- 部署 HTTP/3 也存在着非常大的问题。因为系统内核对 UDP 的优化远远没有达到 TCP 的优化程度,这也是阻碍 QUIC 的一个重要原因。
- 中间设备僵化的问题。这些设备对 UDP 的优化程度远远低于 TCP,据统计使用 QUIC 协议时,大约有 3%~7% 的丢包率。
队头阻塞问题图示
正常情况下的TCP传输过程:(中间表示TCP管道)
HTTP 1.1:多个请求公用一个 TCP 管道,在一个管道中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态,因此数据不能并行请求。
HTTP/2多路复用:(只在应用层实现了并行请求)
HTTP 2.0:由于TCP的机制,如果前面的数据包丢失了,后面的在回退N帧的情况下,即使客户端收到了也要发送方重传。更进一步的,在选择性重传的情况下,一旦超出了发送方维护的滑动窗口,也是需要重传的。
QUIC协议的多路复用:
HTTP 3.0:解决了队头阻塞问题,在 HTTP 2.0 的基础上还实现了数据流的单独传输