HTTP/2 [RFC 7540] (一)—— 概念介绍

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

已有 4 条评论

  1. Keep on writing, great job!

  2. Merely online bank things out ... like the images!
    I attempt to find out by looking at various other
    pictures, also.

  3. You can definitely see your enthusiasm in the work you write.

    The arena hopes for more passionate writers like you who aren't afraid to mention how
    they believe. Always follow your heart.

  4. Wonderful blog! I found it while browsing on Yahoo News.
    Do you have any suggestions on how to get listed in Yahoo News?
    I've been trying for a while but I never seem
    to get there! Cheers

添加新评论