HTTP/0.9
HTTP/0.9 是于 1991 年提出的,主要用于学术交流,需求很简单——用来在网络之间传递 HTML 超文本的内容,所以被称为超文本传输协议 超文本传输协议(Hyper Text Transfer Protocol,HTTP)是一个简单的请求-响应协议,它通常运行在TCP之上
HTTP/0.9的完整请求流程
- 因为 HTTP 都是基于 TCP 协议的,所以客户端先要根据 IP 地址、端口和服务器建立 TCP 连接,而建立连接的过程就是 TCP 协议三次握手的过程
- 建立好连接之后,会发送一个 GET 请求行的信息,如GET /index.html用来获取 index.html。
- 服务器接收请求信息之后,读取对应的 HTML 文件,并将数据以 ASCII 字符流返回给客户端
- HTML 文档传输完成后,断开连接。
HTTP/0.9特点
1. 只有一个请求行
并没有 HTTP 请求头和请求体 GET /index.html
2. 服务器没有返回头信息
3.返回文件内容以 ASCII 字符流传输
返回的文件内容是以 ASCII 字符流来传输的。(因为都是HTML格式的文件,ASCII字符流最合适)
HTTP/1.0
时间:1996年 在浏览器中展示的不单是 HTML 文件了,还包括了 JavaScript、CSS、图片、音频、视频等不同类型的文件。因此支持多种类型的文件下载是 HTTP/1.0 的一个核心诉求 为了让客户端和服务器能更深入地交流,HTTP/1.0 引入了请求头和响应头,它们都是以为 Key-Value 形式保存的,在 HTTP 发送请求时,会带上请求头信息,服务器返回数据时,会先返回响应头信息 那 HTTP/1.0 是怎么通过请求头和响应头来支持多种不同类型的数据呢? 要支持多类型的文件,需要解决几个问题
- 浏览器需要知道服务器返回的数据是什么类型,才能根据不同的类型做针对性处理
- 单个文件数据量越来越大,为了减轻传输性能,服务器会对数据进行压缩后再传输,浏览器需要知道服务器的压缩方法。
- 浏览器要告诉服务器它想要什么语言版本的页面
- 由于增加了不同类型的文件,每种文件编码形式可能不一样,浏览器需要知道文件的编码类型。
HTTP/1.0的方案是通过请求头和响应头来进行协商 在发起请求时候会通过 HTTP 请求头告诉服务器它期待服务器返回什么类型的文件、采取什么形式的压缩、提供什么语言的文件以及文件的具体编码
// 请求头
accept: text/html // 期望服务器返回html类型文件
accept-encoding: gzip, deflate, br // 期望服务器可采用gzip、deflate、br其中一种压缩方式
accept-Charset: ISO-8859-1,utf-8 // 期望返回的文件编码是UTF-8, 或者ISO-8859-1
accept-language: zh-CN,zh // 期望页面优先使用中文
服务器会根据请求头的信息来准备响应数据,但最终浏览器需要根据响应头的信息来处理数据,
// 响应头
content-encoding: br // 压缩方式
content-type: text/html; charset=UTF-8 // 返回文件的类型和编码
HTTP/ 1.0新特性
1. 传输的数据不再仅限于文本, 增加了HEAD、POST等新方法。
2. 引入了响应状态码
状态码是通过响应行的方式来通知浏览器的
3.提供Cache机制
为了减轻服务器压力,提供了Cache机制,用来缓存已下载过的数据
4.引入了HTTP Header(头部)概念,让HTTP请求和响应更加灵活
服务器需要统计客户端的基础信息,所以 HTTP/1.0 的请求头中还加入了用户代理的字段
5.引入了协议版本号的概念
TCP连接的新建成本很高,因为需要客户端和服务器三次握手,并且开始时发送速率较慢(slow start)
HTTP/1.1
时间:1997年1月 目前为止主流版本
1. 引入持久连接
HTTP/1.0 每进行一次 HTTP 通信,都需要经历建立 TCP 连接、传输 HTTP 数据和断开 TCP 连接三个阶段(如下图) 如果在下载每个文件的时候,都需要经历建立 TCP 连接、传输数据和断开连接这样的步骤,无疑会增加大量无谓的开销。 HTTP/1.1 中增加了持久连接的方法,它的特点是在一个 TCP 连接上可以发送多个 HTTP 请求,只要浏览器或者服务器没有明确断开连接,那么该 TCP 连接会一直保持。 重点注意,虽然一个TCP连接可以发送多个HTTP请求,但是: HTTP / 1.1, 单个TCP连接,在同一时刻只能处理一个HTTP请求,意思是说:两个HTTP请求的生命周期不能重叠,也就是不能并行。
从上图可以看出,HTTP 的持久连接可以有效减少 TCP 建立连接和断开连接的次数,这样的好处是减少了服务器额外的负担,并提升整体 HTTP 的请求时长 持久连接在 HTTP/1.1 中是默认开启的,Connection: keep-alive. 如果不想要采用持久连接,可以在 HTTP 请求头中加上Connection: close。目前浏览器中对于同一个域名,默认允许同时建立 6 个 TCP 持久连接。 (http/1.1中的一个tcp链接同时只能发起一个http请求! 浏览器会让每个域名同时最多建立6个tcp链接,也就是说同一个域名同时能支持6个http请求!)
2. 不成熟的HTTP管线化
持久连接虽然能减少 TCP 的建立和断开次数,但是它需要等待前面的请求返回之后,才能进行下一次请求。如果 TCP 通道中的某个请求因为某些原因没有及时返回,那么就会阻塞后面的所有请求,这就是著名的队头阻塞的问题 HTTP/1.1 中试图通过管线化的技术来解决队头阻塞的问题。HTTP/1.1 中的管线化是指将多个 HTTP 请求整批提交给服务器的技术,虽然可以整批发送请求,不过服务器依然需要根据请求顺序来回复浏览器的请求。 HTTP / 2.0中废弃了管道。
队头阻塞
当顺序请求多个文件时,其中一个请求因为某种原因被阻塞时,在后面排队的所有请求也一并被阻塞,这就是队头阻塞。 人们尝试过以下办法来解决队头阻塞问题
- 使用多个域名。
- 将同一页面的资源分散到不同域名,提升并发连接上线,因为浏览器对同一域名的HTTP连接并发最大为6个。
- 引入雪碧图
- 将多张小图合并成一张大图,将多个请求合并成一个请求。
- 带来的问题,某张小图更新了,需要重新请求大图,浪费大量网络带宽
- 将小图内联
- 将图片的二进制数据通过 base64 编码后,把编码数据嵌入到 HTML 或 CSS 文件中,以此来减少网络请求次数;
- 使用webpack等打包工具
- 打包压缩多个JS文件到一个文件中,以一个请求代替多个请求。
- 带来的问题:当某个JS文件变化了,需要重新请求同一个包里的所有JS文件
- 按需加载
- 来减少第一时间的 HTTP 请求次数
3. 提供虚拟主机支持(Host)
在 HTTP/1.0 中,每个域名绑定了一个唯一的 IP 地址,因此一个服务器只能支持一个域名。但是随着虚拟主机技术的发展,需要实现在一台物理主机上绑定多个虚拟主机,每个虚拟主机都有自己的单独的域名,这些单独的域名都公用同一个 IP 地址。 因此,HTTP/1.1 的请求头中增加了 Host 字段,用来表示当前的域名地址,这样服务器就可以根据不同的 Host 值做不同的处理
4. 支持动态生成内容(Chunk transfer 机制)
在设计 HTTP/1.0 时,需要在响应头中设置完整的数据大小,如Content-Length: 901,这样浏览器就可以根据设置的数据大小来接收数据。不过随着服务器端的技术发展,很多页面的内容都是动态生成的,因此在传输数据之前并不知道最终的数据大小,这就导致了浏览器不知道何时会接收完所有的文件数据。 HTTP/1.1 通过引入 Chunk transfer 机制来解决这个问题,服务器会将数据分割成若干个任意大小的数据块,每个数据块发送时会附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志。这样就提供了对动态内容的支持。
5. 引入客户端Cookie、安全机制
- 查看Cookie单独专题知识点
其它
HTTP/ 1.1 在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接
HTTP1.1的主要问题
1. HTTP/1.1对带宽的利用率不理想
HTTP/1.1对带宽的利用率却并不理想,这也是 HTTP/1.1 的一个核心问题 带宽是指每秒最大能发送或者接收的字节数。我们把每秒能发送的最大字节数称为上行带宽,每秒能够接收的最大字节数称为下行带宽 之所以说 HTTP/1.1 对带宽的利用率不理想,是因为 HTTP/1.1 很难将带宽用满。比如我们常说的 100M 带宽,实际的下载速度能达到 12.5M/S,而采用 HTTP/1.1 时,也许在加载页面资源时最大只能使用到 2.5M/S,很难将 12.5M 全部用满。 主要由三个原因导致
1. TCP的慢启动
一旦一个 TCP 连接建立之后,就进入了发送数据状态,刚开始 TCP 协议会采用一个非常慢的速度去发送数据,然后慢慢加快发送数据的速度,直到发送数据的速度达到一个理想状态,我们把这个过程称为慢启动。 慢启动是 TCP 为了减少网络拥塞的一种策略,我们是没有办法改变的。
2. 同时开启了多条 TCP 连接,那么这些连接会竞争固定的带宽
因为有的 TCP 连接下载的是一些关键资源,如 CSS 文件、JavaScript 文件等,而有的 TCP 连接下载的是图片、视频等普通的资源文件,但是多条 TCP 连接之间又不能协商让哪些关键资源优先下载,这样就有可能影响那些关键资源的下载速度了。 资源竞争时无法区分优先级。
3. HTTP/1.1 队头阻塞问题
在 HTTP/1.1 中使用持久连接时,虽然能公用一个 TCP 管道,但是在一个管道中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态。 对于 队头阻塞问题,只要传输层是TCP,就不会得到根本上的解决, http/2 利用流的的机制很大程度的缓解了这个问题,http/3 传输层换成了 UDP 才彻底解决这个问题
总结:慢启动和 TCP 连接之间相互竞争带宽是由于 TCP 本身的机制导致的,而队头阻塞是由于 HTTP/1.1 的机制导致的。
HTTP/2.0
时间: 2015年 HTTP/ 2.0 比 HTTP / 1.1快了多少,感受一下 https://http2.akamai.com/demo 针对HTTP/1.1的问题,HTTP/2 的解决方案可以总结为:一个域名只使用一个 TCP 长连接和消除队头阻塞问题。
多路复用机制(Multiplexing) HTTP/2 使用了多路复用技术,可以将请求分成一帧一帧的数据去传输,这样带来了一个额外的好处,就是当收到一个优先级高的请求时,比如接收到 JavaScript 或者 CSS 关键资源的请求,服务器可以暂停之前的请求来优先处理关键资源的请求。 每个请求都有一个对应的ID, 如stream1, stream2
多路复用的实现原理
HTTP/2 添加了一个二进制分帧层
- 首先,浏览器准备请求数据,包括了请求行、请求头等信息,如果是 POST 方法,那么还要有请求体。
- 这些数据经过二进制分帧层处理之后,会被转换为一个个带有请求 ID 编号的帧,通过协议栈将这些帧发送给服务器。
- 服务器接收到所有帧之后,会将所有相同 ID 的帧合并为一条完整的请求信息。
- 然后服务器处理该条请求,并将处理的响应行、响应头和响应体分别发送至二进制分帧层。
- 同样,二进制分帧层会将这些响应数据转换为一个个带有请求 ID 编号的帧,经过协议栈发送给浏览器。
- 浏览器接收到响应帧之后,会根据 ID 编号将帧的数据提交给对应的请求。
从上面的流程可以看出,通过引入二进制分帧层,就实现了 HTTP 的多路复用技术。 HTTP / 2.0 与 HTTP / 1.1 发生改变的只是传输方式,通信语言并没有改变,所以不需要为HTTP / 2.0 重建生态。 ps. http/2是没必要用雪碧图了
HTTP/2.0的特性
1. 引入二进制分帧层,实现HTTP的多路复用
2. 可以设置请求优先级
3. 头部压缩
无论是 HTTP/1.1 还是 HTTP/2,它们都有请求头和响应头,这是浏览器和服务器的通信语言。HTTP/2 对请求头和响应头进行了压缩。
4. 服务器推送
除了设置请求的优先级外,HTTP/2 还可以直接将数据提前推送到浏览器。 可以想象这样一个场景,当用户请求一个 HTML 页面之后,服务器知道该 HTML 页面会引用几个重要的 JavaScript 文件和 CSS 文件,那么在接收到 HTML 请求之后,附带将要使用的 CSS 文件和 JavaScript 文件一并发送给浏览器,这样当浏览器解析完 HTML 文件之后,就能直接拿到需要的 CSS 文件和 JavaScript 文件,这对首次打开页面的速度起到了至关重要的作用。
5. 增强了安全性,事实上要求加密通信
HTTP / 2.0缺陷
TCP的队头阻塞
我们把在 TCP 传输过程中,由于单个数据包的丢失而造成的阻塞称为 TCP 上的队头阻塞。 虽然 HTTP/2 解决了应用层面的队头阻塞问题,不过和 HTTP/1.1 一样,HTTP/2 依然是基于 TCP 协议的,而 TCP 最初就是为了单连接而设计的。可以把 TCP 连接看成是两台计算机之前的一个虚拟管道,计算机的一端将要传输的数据按照顺序放入管道,最终数据会以相同的顺序出现在管道的另外一头。 HTTP /1.1 协议栈中 TCP传输数据如图 如果在数据传输的过程中,有一个数据因为网络故障或者其他原因而丢包了,那么整个 TCP 的连接就会处于暂停状态,需要等待丢失的数据包被重新传输过来。
HTTP /2.0 的传输过程
在 HTTP/2 中,多个请求是跑在一个 TCP 管道中的,如果其中任意一路数据流中出现了丢包的情况,那么就会阻塞该 TCP 连接中的所有请求。这不同于 HTTP/1.1,使用 HTTP/1.1 时,浏览器为每个域名开启了 6 个 TCP 连接,如果其中的 1 个 TCP 连接发生了队头阻塞,那么其他的 5 个连接依然可以继续传输数据。 当系统达到了 2% 的丢包率时,HTTP/1.1 的传输效率反而比 HTTP/2 表现得更好。
TCP建立连接的延时
除了 TCP 队头阻塞之外,TCP 的握手过程也是影响传输效率的一个重要因素 网络延迟又称为 RTT(Round Trip Time) 我们把从浏览器发送一个数据包到服务器,再从服务器返回数据包到浏览器的整个往返时间称为 RTT(如下图)。RTT 是反映网络性能的一个重要指标。 我们知道 HTTP/1 和 HTTP/2 都是使用 TCP 协议来传输的,而如果使用 HTTPS 的话,还需要使用 TLS 协议进行安全传输,而使用 TLS 也需要一个握手过程,这样就需要有两个握手延迟过程。
- 在建立 TCP 连接的时候,需要和服务器进行三次握手来确认连接成功,也就是说需要在消耗完 1.5 个 RTT 之后才能进行数据传输
- 进行 TLS 连接,TLS 有两个版本——TLS1.2 和 TLS1.3,每个版本建立连接所花的时间不同,大致是需要 1~2 个 RTT
TCP 协议僵化
TCP 协议存在队头阻塞和建立连接延迟等缺点,那我们是不是可以通过改进 TCP 协议来解决这些问题呢,非常困难 因为
- 中间设备僵化
- 操作系统更新滞后
HTTP/3.0
在 HTTP/1.1 时代,为了提升并行下载效率,浏览器为每个域名维护了 6 个 TCP 连接;而采用 HTTP/2 之后,浏览器只需要为每个域名维护 1 个 TCP 持久连接,同时还解决了 HTTP/1.1 队头阻塞的问题。(TCP层仍然存在数据包级别的队头阻塞问题)
QUIC协议
HTTP/3 选择了一个折衷的方法——UDP 协议,基于 UDP 实现了类似于 TCP 的多路数据流、传输可靠性等功能,我们把这套功能称为 QUIC 协议 HTTP/3 中的 QUIC 协议集合了以下几点功能
- 实现了类似 TCP 的流量控制、传输可靠性的功能
- 虽然 UDP 不提供可靠性的传输,但 QUIC 在 UDP 的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些 TCP 中存在的特性。
- 集成了 TLS 加密功能
- 目前 QUIC 使用的是 TLS1.3,相较于早期版本 TLS1.3 有更多的优点,其中最重要的一点是减少了握手所花费的 RTT 个数
- 实现了 HTTP/2 中的多路复用功能
- 和 TCP 不同,QUIC 实现了在同一物理连接上可以有多个独立的逻辑数据流(如下图)。实现了数据流的单独传输,就解决了 TCP 中队头阻塞的问题。
- 和 TCP 不同,QUIC 实现了在同一物理连接上可以有多个独立的逻辑数据流(如下图)。实现了数据流的单独传输,就解决了 TCP 中队头阻塞的问题。
- 实现了快速握手功能
- 由于 QUIC 是基于 UDP 的,所以 QUIC 可以实现使用 0-RTT 或者 1-RTT 来建立连接,这意味着 QUIC 可以用最快的速度来发送和接收数据,这样可以大大提升首次打开页面的速度
HTTP / 3 挑战
- 从目前的情况来看,服务器和浏览器端都没有对 HTTP/3 提供比较完整的支持。Google版本的QUIIC和官方版本QUIC还存在非常大的差异
- 部署 HTTP/3 也存在着非常大的问题。因为系统内核对 UDP 的优化远远没有达到 TCP 的优化程度,这也是阻碍 QUIC 的一个重要原因。
- 中间设备僵化的问题。这些设备对 UDP 的优化程度远远低于 TCP,据统计使用 QUIC 协议时,大约有 3%~7% 的丢包率。
总结
影响 HTTP/1.1 效率的三个主要因素:
- TCP 的慢启动、
- 多条 TCP 连接竞争带宽
- 队头阻塞。
接下来我们分析了 HTTP/2 是如何采用多路复用机制来解决这些问题的。 多路复用是通过在协议栈中添加二进制分帧层来实现的,有了二进制分帧层还能够实现请求的优先级、服务器推送、头部压缩等特性,从而大大提升了文件传输效率。
参考
- 浏览器工作原理 from GeekTime
- 透视HTTP协议 from GeekTime
- 你猜一个 TCP 连接上面能发多少个 HTTP 请求
- HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
- HTTP/2对比HTTP/1.1,特性是什么?是如何解决队头阻塞与压缩头部的?
- https://juejin.cn/post/6985444039340326948#heading-9
- HTTP/1.1报文详解