协议版本

HTTP


# HTTP0.9

总的来说,当时的需求很简单,就是用来传输体积很小的 HTML 文件,所以 HTTP/0.9 的实现有以下三个特点。

  • 第一个是只有一个请求行,并没有 HTTP 请求头和请求体,因为只需要一个请求行就可以完整表达客户端的需求了。
  • 第二个是服务器也没有返回头信息,这是因为服务器端并不需要告诉客户端太多信息,只需要返回数据就可以了。
  • 第三个是返回的文件内容是以 ASCII 字符流来传输的,因为都是 HTML 格式的文件,所以使用 ASCII 字节码来传输是最合适的。

# 问题

万维网的高速发展带来了很多新的需求,而 HTTP/0.9 已经不能适用新兴网络的发展,所以这时就需要一个新的协议来支撑新兴网络,这就是 HTTP/1.0 诞生的原因。不过在详细分析 HTTP/1.0 之前,我们先来分析下新兴网络都带来了哪些新需求。

首先在浏览器中展示的不单是 HTML 文件了,还包括了 JavaScript、CSS、图片、音频、视频等不同类型的文件。因此支持多种类型的文件下载是 HTTP/1.0 的一个核心诉求 而且文件格式不仅仅局限于 ASCII 编码,还有很多其他类型编码的文件

# HTTP1.0 - 非标准

# 引入头字段

为了解决 0.9 版本不支持多种类型文件的传输问题,让客户端和服务器能更深入地交流,HTTP/1.0 引入了请求头和响应头,它们都是以为 Key-Value 形式保存的,在 HTTP 发送请求时,会带上请求头信息,服务器返回数据时,会先返回响应头信息。

# 如何通过头字段来支持多种类型数据的传输

要支持多种类型的文件,我们就需要解决以下几个问题:

  • 首先,浏览器需要知道服务器返回的数据是什么类型的,然后浏览器才能根据不同的数据类型做针对性的处理。
  • 其次,由于万维网所支持的应用变得越来越广,所以单个文件的数据量也变得越来越大。为了提升传输性能,服务器会对数据进行压缩后再传输,所以浏览器需要知道服务器压缩的方法。
  • 再次,由于万维网是支持全球范围的,所以需要提供国际化的支持,服务器需要对不同的地区提供不同的语言版本,这就需要浏览器告诉服务器它想要什么语言版本的页面。
  • 最后,由于增加了各种不同类型的文件,而每种文件的编码形式又可能不一样,为了能够准确地读取文件,浏览器需要知道文件的编码类型

基于以上问题,HTTP/1.0 的方案是通过请求头和响应头来进行协商,在发起请求时候会通过 HTTP 请求头告诉服务器它期待服务器返回什么类型的文件、采取什么形式的压缩、提供什么语言的文件以及文件的具体编码。最终发送出来的请求头内容如下:

accept: text/html
accept-encoding: gzip, deflate, br
accept-Charset: ISO-8859-1,utf-8
accept-language: zh-CN,zh
1
2
3
4

# 其他特性

HTTP/1.0 除了对多文件提供良好的支持外,还依据当时实际的需求引入了很多其他的特性,这些特性都是通过请求头和响应头来实现的。下面我们来看看新增的几个典型的特性:

  • 状态码:有的请求服务器可能无法处理,或者处理出错,这时候就需要告诉浏览器服务器最终处理该请求的情况,这就引入了状态码。状态码是通过响应行的方式来通知浏览器的。
  • 缓存控制:为了减轻服务器的压力,在 HTTP/1.0 中提供了 Cache 机制(If-Modified-Since/Expires ),用来缓存已经下载过的数据。
  • ua 信息:服务器需要统计客户端的基础信息,比如 Windows 和 macOS 的用户数量分别是多少,所以 HTTP/1.0 的请求头中还加入了用户代理(即 ua 信息)的字段

# 存在的问题

  • 队头阻塞问题

HTTP/1.0 每进行一次 HTTP 通信,都需要经历建立 TCP 连接、传输 HTTP 数据和断开 TCP 连接三个阶段,值得注意的是,所有的请求任务都被放在一个任务队列中串行执行,一旦队首的请求处理太慢,就会阻塞后面请求的处理。这就是著名的 HTTP 队头阻塞问题

# HTTP1.1

不过随着技术的继续发展,需求也在不断迭代更新,很快 HTTP/1.0 也不能满足需求了,所以 HTTP/1.1 又在 HTTP/1.0 的基础之上做了大量的更新。接下来我们来看看 HTTP/1.0 遇到了哪些主要的问题,以及 HTTP/1.1 又是如何改进的。 为了解决 1.0 版本中提到的队头阻塞问题,1.1 中引入了持久链接:

# Connection: keep-alive

HTTP/1.0 每进行一次 HTTP 通信,都需要经历建立 TCP 连接、传输 HTTP 数据和断开 TCP 连接三个阶段。 为了解决这个问题,HTTP/1.1 中增加了持久连接的方法,它的特点是在一个 TCP 连接上可以传输多个 HTTP 请求,只要浏览器或者服务器没有明确断开连接,那么该 TCP 连接会一直保持。

keep-alive 也是一个通用标头,一般 Connection 都会和 keep-alive 一起使用,keep-alive 有两个参数,一个是 timeout;另一个是 max,它们的主要表现形式如下

Connection: Keep-Alive
Keep-Alive: timeout=5, max=1000
1
2
  • timeout: 指的是空闲连接必须打开的最短时间,也就是说这次请求的连接时间不能少于 5 秒
  • max: 指的是在连接关闭之前服务器所能够收到的最大请求数

# TCP 保活计时器

防止持久连接一直空闲无法断开

HTTP 的持久连接可以有效减少 TCP 建立连接和断开连接的次数,减少了服务器额外的负担,持久连接在 HTTP/1.1 中是默认开启的,无需手动设置 Connection:Keep-Alive,不想要采用持久连接:Connection: close

目前浏览器中对于同一个域名,默认允许同时建立 6 ~ 8 个 TCP 持久连接,这个数字为什么不能更大呢?实际上这是出于公平性的考虑,每个连接对于服务端来说都会带来一定开销,如果浏览器不加以限制,一个性能好带宽足的终端就可能耗尽服务端所有资源,造成其他人无法使用。

# 域名分片

其实除了持久连接之外,还有另外一种方式来解决 1.0 中存在的队头阻塞问题,那就是域名分片。

虽然一个域名可以同时并发 6~8 个长连接,但如果某个网页的资源非常多,那单个域名下同时也只能有 6-8 个资源在并发请求,所以,还可以通过添加多个域名的方式,将静态资源分配到不同的域名上来增加并发。

# 虚拟主机支持 - Host 头字段

随着虚拟主机技术的发展,需要实现在一台物理主机上绑定多个虚拟主机,每个虚拟主机都有自己的单独的域名,这些单独的域名都公用同一个 IP 地址。因此,HTTP/1.1 的请求头中增加了 Host 字段,用来表示当前的域名地址,这样服务器就可以根据不同的 Host 值做不同的处理。

# 支持响应分块(Transfer-Encoding: chunked)

  • 定长内容:Content-Length,一般定长内容的响应会具有Content-Length字段,来指明响应包体的长度。
  • 不定长内容:Transfer-Encoding: chunked,表示分块传输数据,设置这个字段后会自动产生两个效果:
    • Content-Length 字段会被忽略,同时每个块的开头会添加上当前分块的长度
    • 基于长连接持续推送动态内容,最后使用一个 0 长度的块作为终止块,表示数据发送完成

# 更完善的缓存机制

在 HTTP1.0 中主要使用 header 里的 If-Modified-Since,Expires 来做为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略例如ETagIf-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓存策略

# HTTP2.0

HTTP/2 是一个二进制协议(1.x 使用的是文本传输),其基于“帧”的结构设计

# 重要概念

  • Connection:1 个 TCP 连接,包含 1 个或者多个 stream。所有通信都在一个 TCP 连接上完成,此连接可以承载任意数量的双向数据流。

  • Stream:一个双向通信的数据流,包含 1 条或者多条 Message。每个数据流都有一个唯一的标识符和可选的优先级信息,用于承载双向消息。

  • Message:对应 HTTP/1.1 中的请求 request 或者响应 response,包含 1 条或者多条 Frame。

  • Frame:最小通信单位,以二进制压缩格式存放内容。来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。

在 HTTP/1.1 中的一个消息是由 Start Line + header + body 组成的,而 HTTP/2 中一个消息是由 HEADER frame + 若干个 DATA frame 组成的,如下图:

HTTP/2 当中废除了起始行的概念,将起始行中的请求方法、URI、状态码转换成了头字段,不过这些字段都有一个":"前缀,用来和其它请求头区分开。

# 头部压缩

HTTP 1.1 版本会出现 User-Agent、Cookie、Accept、Server、Range 等字段可能会占用几百甚至几千字节,而 Body 却经常只有几十字节,所以导致头部偏重,HTTP 2.0 使用 HPACK 算法进行压缩。

  • 在服务器和客户端之间建立哈希表,将用到的字段存放在这张表中,那么在传输的时候对于之前出现过的值,只需要把索引(比如 0,1,2,...)传给对方即可,对方拿到索引查表就行了
  • 对于整数和字符串进行哈夫曼编码

# 服务端推送

在 HTTP/2 当中,服务器已经不再是完全被动地接收请求,响应请求,它也能新建 stream 来给客户端发送消息,当 TCP 连接建立之后,比如浏览器请求一个 HTML 文件,服务器就可以在返回 HTML 的基础上,将 HTML 中引用到的其他资源文件一起返回给客户端,减少客户端的等待

# 二进制分帧

首先,HTTP/2 认为明文传输对机器而言太麻烦了,不方便计算机的解析,因为对于文本而言会有多义性的字符,比如回车换行到底是内容还是分隔符,在内部需要用到状态机去识别,效率比较低。于是 HTTP/2 干脆把报文全部换成二进制格式,全部传输 01 串,方便了机器的解析。

原来 Headers + Body 的报文格式如今被拆分成了一个个二进制的帧,用 Headers 帧存放头部字段Data 帧存放请求体数据。分帧之后,服务器看到的不再是一个个完整的 HTTP 请求报文,而是一堆乱序的二进制帧。这些二进制帧不存在先后关系,因此也就不会排队等待,也就没有了 HTTP 的队头阻塞问题。

通信双方都可以给对方发送二进制帧,这种二进制帧的双向传输的序列,也叫做流(Stream)。HTTP/2 用流来在一个 TCP 连接上来进行多个数据帧的通信,这就是多路复用的概念。

可能你会有一个疑问,既然是乱序首发,那最后如何来处理这些乱序的数据帧呢?

首先要声明的是,所谓的乱序,指的是不同 ID 的 Stream 是乱序的,但同一个 Stream ID 的帧一定是按顺序传输的。二进制帧到达后对方会将 Stream ID 相同的二进制帧组装成完整的请求报文和响应报文。

# 帧结构

  • 每个帧分为帧头帧体。先是三个字节的帧长度,这个长度表示的是帧体的长度。

  • 然后是帧类型,大概可以分为数据帧控制帧两种。数据帧用来存放 HTTP 报文,控制帧用来管理流的传输。

  • 接下来的一个字节是帧标志,里面一共有 8 个标志位,常用的有 END_HEADERS 表示头数据结束,END_STREAM 表示单方向数据发送结束。

  • 后 4 个字节是 Stream ID, 也就是流标识符,有了它,接收方就能从乱序的二进制帧中选择出 ID 相同的帧,按顺序组装成请求/响应报文。

# 多路复用

HTTP 1.x 中,如果想并发多个请求,必须使用多个 TCP 链接,且浏览器为了控制资源,还会对单个域名有 6-8 个的 TCP 链接请求限制。

HTTP2 中:

  • 同域名下所有通信都在单个连接上完成。
  • 单个连接可以承载任意数量的双向数据流。
  • 数据流以消息的形式发送,而消息又由一个或多个帧组成,多个帧之间可以乱序发送,因为根据帧首部的流标识可以重新组装,也就是 Stream ID,流标识符,有了它,接收方就能从乱序的二进制帧中选择 ID 相同的帧,按照顺序组装成请求/响应报文。

# HTTP3.0

# HTTP2 的缺点

  • TCP 以及 TCP+TLS 建立连接的延时,HTTP/2 使用 TCP 协议来传输的,而如果使用 HTTPS 的话,还需要使用 TLS 协议进行安全传输,而使用 TLS 也需要一个握手过程,在传输数据之前,导致我们需要花掉 3 ~ 4 个 RTT。

  • TCP 的队头阻塞并没有彻底解决。在 HTTP/2 中,多个请求是跑在一个 TCP 管道中的。但当 HTTP/2 出现丢包时,整个 TCP 都要开始等待重传,那么就会阻塞该 TCP 连接中的所有请求

# 解决方案

Google 在推 SPDY 的时候就已经意识到了这些问题,于是就另起炉灶搞了一个基于 UDP 协议的 QUIC 协议,让 HTTP 跑在 QUIC 上而不是 TCP 上。主要特性如下:

  • 实现了类似 TCP 的流量控制、传输可靠性的功能。虽然 UDP 不提供可靠性的传输,但 QUIC 在 UDP 的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些 TCP 中存在的特性
  • 实现了快速握手功能。由于 QUIC 是基于 UDP 的,所以 QUIC 可以实现使用 0-RTT 或者 1-RTT 来建立连接,这意味着 QUIC 可以用最快的速度来发送和接收数据。
  • 集成了 TLS 加密功能。目前 QUIC 使用的是 TLS1.3,相较于早期版本 TLS1.3 有更多的优点,其中最重要的一点是减少了握手所花费的 RTT 个数。
  • 多路复用,彻底解决 TCP 中队头阻塞的问题。
Last Updated: 10/21/2024, 4:15:17 PM