http2 以及 go 实现:第一部分

随着 HTTP 的广泛应用,HTTP/1.0 和 HTTP/1.1 的底层传输模式越来越无法满足对高性能的追求。HTTP/1.0 和 HTTP/1.1 存在以下问题:

  • 为了实现并发从而减少延迟,HTTP/1.0 和 HTTP/1.1 客户端需要通过多个 TCP 连接发起的多个请求。
  • HTTP 头部的重复性和冗余性会引发不必要的网络流量,并引发初始的 TCP 拥塞窗口被快速填满。当多个请求在一个新的 TCP 连接上处理时,上述情况就会导致过多的延迟。

于是,HTTP/2 应运而生。

HTTP/2 支持 HTTP/1.1 的所有核心特性,但是针对 HTTP/1.x 的问题做出了几点改动:

  • HTTP/2 中的基本协议单元是帧(frame),不同的用途使用不同的帧类型。例如,HEADERSDATA 帧组成了 HTTP 请求和响应的基础。二进制消息帧的使用也使得消息处理更加高效。
  • 请求/响应多路复用。每个 HTTP 请求/响应交互与其自身的流(stream)相关联,流与流之间相互独立。因此,请求或者响应的阻塞并不会阻碍其他流的处理。
  • 流控制和优先级控制,以确保高效使用多路复用的流。
  • 支持服务端推送(新特性)
  • 报头压缩,以减少请求大小。

HTTP/2 协商

HTTP/2 连接是运行在 TCP 连接之上的应用层协议。在创建 HTTP/2 连接之前,需要确定服务端是否支持 HTTP/2。

HTTP/2 版本标识

  • h2:基于 TLS(Transport Layer Security)的 HTTP/2 使用此标识
  • h2c:基于使用明文的 TCP 的 HTTP/2。

对于 “http” URI

协商过程如下:
h2c 协商

有几个需要注意的地方:

  • 请求 header 必须Upgrade: h2c
  • 请求 header 必须包含一个 HTTP2-Settings(内容详见rfc7540#section-3.2.1
  • 不支持 HTTP/2 的服务端直接忽略 Upgrade
  • 如果 header 包含 Upgrade: h2,服务端直接忽略。
  • 服务端发送的第一个 HTTP/2 帧必须是一个由 SETTINGS 帧组成的服务端连接序言(preface)。
  • 收到 101 响应后,客户端必须发送一个包含 SETTINGS 帧的连接序言

连接序言作用是作为使用的协议的最终确认,以及用来建立 HTTP/2 连接的初始设置。客户端和服务端的连接序言并不相同。

对于 “https” URI

另一方面,发起 https 请求的客户则使用 TLS-ALPN(带应用层协议协商扩展的TLS)。

使用 h2 标识。客户端不能发送,并且服务端也不能选择 h2c 标识。

一旦 TLS 协商完成了,客户端和服务端必须发送连接序言。

帧(frame)

一旦建立了 HTTP/2 连接,客户端和服务端之间就可以开始交换帧了。

一个帧是由一个固定的 9 个字节的帧头以及后面跟着的不定长度的内容组成。
frame 格式

  • Length:frame payload 的大小。值不能超过2^14 (16,384),除非接收者使用 SETTINGS_MAX_FRAME_SIZE 设置了一个更大的值。该值不能超过 SETTINGS_MAX_FRAME_SIZE
  • Type:定义该帧的格式和语义。
  • Flags:指定特定的帧类型的某些状态信息。
  • R:保留位
  • Stream Identifier:流标识符
  • Frame Payload:帧内容

流(stream)

流指的是在 HTTP/2 连接中,客户端与服务器之间的独立双向帧交换序列。它有几个重要特性:

  • 单个 HTTP/2 连接可以包含多个并发打开的流,来自不同流的帧交错传输
  • 可以单方建立并使用流,或者由客户端/服务端共享流
  • 流可以由任何一端关闭
  • 流上发送的帧顺序是很重要的,因为接收者是以它们接收到的帧顺序来处理这些帧的。
  • 流标识符是一个整数,由初始化该流的那一端赋值。

流状态

一个流的生命周期如下:

  • idle:空闲状态。所有流的初始状态。
  • reserved(local):当发送了 PUSH_PROMISE 帧之后,流便处于该状态。通过将流与由远端初始化的处于打开状态的流相关联,PUSH_PROMISE 帧保留了处于空闲状态的流。位于此状态的流不能发送除了 HEADERS / RST_STREAM / PRIORITY 之外的帧
  • reserved(remote):当流处于该状态时,表明该流被远端保留。
  • open:任意一端都可以使用处于该状态的流来发送任意类型的帧。
  • half-closed(local):不能使用处于该状态的流来发送除了 WINDOW_UPDATE / PRIORITY / RST_STREAM 之外的帧。
  • half-closed (remote):当流处于该状态时,对端就不再会使用它来发送帧了。
  • closed:流的终止状态。

流标识符

流是由一个无符号的 31 位整型所标识的。

  • 由客户端初始化的流必须使用奇数流标识符,而由服务端初始化的则必须使用偶数流标识符。
  • 而标识符为 0x0 的流则被用于连接控制消息,不能用来建立任何新流。
  • 升级为 HTTP/2 的 HTTP/1.1 请求响应时会带流标识符 0x1。升级完成后,对于客户端来说,流 0x1 则处于“half-closed(local)”状态。因此,从 HTTP/1.1 升级上来的客户端不能选择 0x1 作为新的流标识符。
  • 新建立的流的标识符数值上必须大于初始端所有已经打开或者保留的流。

流标识符不能被复用。因此,长期存在的连接会导致可用流标识符的耗尽。在这种情况下,如果客户端无法建立一个新的流 ,那么它会创建一个新连接。而当服务端无法建立新的流时,则会发送一个 GOAWAY 帧,以迫使客户端建立新连接。

参考