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 首先引入了帧的概念,每一帧都是一个数据包,不同的数据包用一个包类型来区分,不同的数据包就是不同的指令,有以下几种类型:

  1. DATA
  2. HEADERS
  3. PRIORITY
  4. RST_STREAM
  5. SETTINGS
  6. PUSH_PROMISE
  7. PING
  8. GOAWAY
  9. WINDOW_UPDATE
  10. CONTINUATION

具体可以看 http://httpwg.org/specs/rfc7540.html 里的 Section 6。

HTTP/2 对数据可以以分包的形式传输,借助CONTINUATION指令,可以继续传输未传输完的内容。

这么多指令里,DATAHEADERS 其实已经可以覆盖 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,关注我

标签: web

添加新评论