HTTP/2 作为 SPDY 的后继者,目的是为了减少 Web 服务的延迟,完善 HTTP/1.1 提供的功能,而后发展成型的一套应用层协议。目前,大部分现代浏览器都已经支持 HTTP/2 的功能。
本文将对 HTTP/2 与 HTTP/1.1 进行比较,让我们能够对 HTTP/2 有一个基本的了解。
有什么亮点
HTTP/2 是为了解决 HTTP/1.1 高延迟,连接无法多路复用等问题而生的。HTTP/2 完全兼容 HTTP/1.1 的特性; HTTP/2 提供了服务端 Push 给客户端的能力,在以往只有用 Long Polling 或者 Websocket 做到。
以前我们对 HTTP 协议做压缩的时候,我们都只能对 HTTP Body 部分进行压缩,而 HTTP/2 中,提供了对报文头部的压缩,使得我们能减少传输的字节数。
HTTP/2 在传输层引入了 Stream 的概念,使得多个报文的传输不再依次等待,多路复用连接的方式充分利用了带宽。
帧(Frame) 的定义
我们来讲讲他们之间报文的不同,HTTP/2 的报文比 HTTP/1.1 的报文复杂许多,不像 HTTP/1.1 只有 Header 和 Body 来定义行为。HTTP/2 首先引入了帧的概念,每一帧都是一个数据包,不同的数据包用一个包类型来区分,不同的数据包就是不同的指令,有以下几种类型:
- DATA
- HEADERS
- PRIORITY
- RST_STREAM
- SETTINGS
- PUSH_PROMISE
- PING
- GOAWAY
- WINDOW_UPDATE
- CONTINUATION
具体可以看 http://httpwg.org/specs/rfc7540.html 里的 Section 6。
HTTP/2 对数据可以以分包的形式传输,借助CONTINUATION
指令,可以继续传输未传输完的内容。
这么多指令里,DATA
和 HEADERS
其实已经可以覆盖 HTTP/1.1 里的内容。剩下的指令除了实现Server Push
的功能,还为了针对单连接多 Stream 的优化,做了一些流量控制 (Flow Control) 的命令,如:WINDOW_UPDATE,以解决不同 Stream 之间互相干扰的问题。
帧结构
HTTP/2 的帧结构如下:
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
各字段的长度在上面已有标识,那么分别解释下这些字段的意思:
- Length: Frame Payload 的长度,不要大于 2^14 (16384),除非在 SETTINGS 里设置了一个比 SETTINGS_MAX_FRAME_SIZE 大的值。
- Type: 代表帧类型,帧类型定义了这个帧的格式和语义,如果帧类型是未知的数值,那么实现方应当无视这个帧
- Flags: 个别的帧所需要的标记位
- R: 一个保留位,它的语义尚未定义,必须保留并设为0.
- Stream Identifier: 一个31位长度的流标示符,在下面将会介绍帧标识符的作用。
Stream Identifier
流标识符(后面成为 Stream ID),用来标识这一次帧的交换,如果是客户端发起的 Stream,那么流标识符必然是一个奇数,如果是服务端发起的 Stream (PUSH),那么流标识符一定是一个偶数。0 为标识的 Stream ID 已经被用做连接控制的消息,因此实现方不可作业务使用。如果是从 HTTP/1.1 升级成为 HTTP/2 的时候,那么升级之后的服务端帧响应就作为对标识符1的应答,然后这个 Stream 就进入 half-closed (local) 状态,因此,一个从 HTTP/1.1 升级成为 HTTP/2 的客户端不能选择 1 作为 Stream ID。
一个新建的 Stream ID 必须高于所有已知的 Stream ID。
所有的 Stream 都在一个状态机中进行状态转换,Stream ID 不可重用。如果一个长连接把 Stream ID 耗尽,不能生成新的 Stream ID 的时候,如果是客户端,这时候可以进行重连;服务端则发送一个 GOAWAY 帧报告给客户端,强制客户端重新连接服务端。
Stream States
+--------+
send PP | | recv PP
,--------| idle |--------.
/ | | \
v +--------+ v
+----------+ | +----------+
| | | send H / | |
,------| reserved | | recv H | reserved |------.
| | (local) | | | (remote) | |
| +----------+ v +----------+ |
| | +--------+ | |
| | recv ES | | send ES | |
| send H | ,-------| open |-------. | recv H |
| | / | | \ | |
| v v +--------+ v v |
| +----------+ | +----------+ |
| | half | | | half | |
| | closed | | send R / | closed | |
| | (remote) | | recv R | (local) | |
| +----------+ | +----------+ |
| | | | |
| | send ES / | recv ES / | |
| | send R / v send R / | |
| | recv R +--------+ recv R | |
| send R / `----------->| |<-----------' send R / |
| recv R | closed | recv R |
`----------------------->| |<----------------------'
+--------+
send: endpoint sends this frame
recv: endpoint receives this frame
H: HEADERS frame (with implied CONTINUATIONs)
PP: PUSH_PROMISE frame (with implied CONTINUATIONs)
ES: END_STREAM flag
R: RST_STREAM frame
Stream 的生命周期如上图所示,一个 Stream 在不同阶段有不同的状态:
- idle: 空闲状态,所有的 Stream 一开始都是空闲状态。
- reserved(local): 服务端发送了一个 PUSH_PROMISE 帧之后,Stream 就进入了这个状态,在这个状态下,服务端只可发送 HEADERS, RST_STREAM 或者 PRIORTY 帧,它可能收到 RST_STREAM, PRIORTY 或者 WINDOW_UPDATE 帧,除此以外其他的帧都应该被认为是连接出错 (connection error)
- reserved(remote): 客户端收到了一个 PUSH_PROMISE 之后,客户端就进入了这个状态,它只可以接收 HEADERS 和 RST_STREAM。同时它可能向服务端发送 RST_STREAM, PRIORTY 或者 WINDOW_UPDATE;除了 HEADERS, RST_STREAM 或者 PRIORTY,其他帧都被认为是连接出错。
- open: 如果一个 Stream 是 open 状态下的话,则可以发送或者接受任何类型的帧,如果有一端发送的帧带上了 END_STREAM 标记(flag)的话,那么,这个帧就会进入到 half-closed 的状态:发送方进入 half-closed(local) 状态,接收方进入 half-closed(remote) 状态
- half-closed(local): 一端的流如果进入此状态,则不可以发送除了 WINDOW_UPDATE,PRIORITY,RST_STREAM 以外的帧,如果收到带上 END_STREAM 的帧或者任意一方发送 RST_STREAM,那么就进入到 close 状态,Stream 的生命周期结束。
- half-closed(remote): 如果一端的流进入此状态,那么它收到除了 WINDOW_UPDATE,PRIORITY,RST_STREAM 以外的帧的话,就必须响应一个 STREAM_CLOSED 错误。在这个状态下,这一段可以发送任何帧给对方(因为对方关闭了发送窗口),如果想要关闭这个 Stream,那么此方就可以发送一个帧,带上 END_STREAM 标记,或者任意一方发送 RST_STREAM.
- closed: 这个状态是终结状态。除了 PRIORITY 帧,其他帧都不应该被发送。
那么这篇文章已经把帧这个概念介绍完了,下一次我们看看完整的 HTTP 信息交换是如何完成的,以及还有 Server Push 是如何实现的。
非常欢迎一起探讨!
关注TalkWithMobile,关注我
本文由 Gemini Wen 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Dec 21, 2020 at 08:20 pm