Skip to content
On this page

下面的这张图对比了 HTTP/1、HTTPS 和 HTTP/2 的协议栈,你可以清晰地看到,HTTP/2 是建立在“HPack”“Stream”“TLS1.2”基础之上的,比 HTTP/1、HTTPS 复杂了一些。

连接前言

由于 HTTP/2“事实上”是基于 TLS,所以在正式收发数据之前,会有 TCP 握手和 TLS 握手,这两个步骤相信你一定已经很熟悉了,所以这里就略过去不再细说。

TLS 握手成功之后,客户端必须要发送一个“连接前言”(connection preface),用来确认建立 HTTP/2 连接。

这个“连接前言”是标准的 HTTP/1 请求报文,使用纯文本的 ASCII 码格式,请求方法是特别注册的一个关键字“PRI”,全文只有 24 个字节:

js
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n

只要服务器收到这个“有魔力的字符串”,就知道客户端在 TLS 上想要的是 HTTP/2 协议,而不是其他别的协议,后面就会都使用 HTTP/2 的数据格式。

nginx 开启 http2 也比较简单
yaml
server {
    listen       443 http2 ssl; # 443 端口 http2 ssl
    server_name  alvin.run;
    #... 这是是上面的配置
}

头部压缩 (略)

上文讲过

二进制帧

HTTP/2 中传输的帧结构如下图所示

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

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

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

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

流的状态变化

从前面可以知道,在 HTTP/2 中,所谓的流,其实就是二进制帧的双向传输的序列。那么在 HTTP/2 请求和响应的过程中,流的状态是如何变化的呢?

HTTP/2 其实也是借鉴了 TCP 状态变化的思想,根据帧的标志位来实现具体的状态改变。这里我们以一个普通的请求-响应过程为例来说明:

最开始两者都是空闲状态,当客户端发送 Headers 帧后,开始分配 Stream ID, 此时客户端的流打开, 服务端接收之后服务端的流也打开,两端的流都打开之后,就可以互相传递数据帧和控制帧了。

当客户端要关闭时,向服务端发送 END_STREAM 帧,进入半关闭状态, 这个时候客户端只能接收数据,而不能发送数据。

服务端收到这个 END_STREAM 帧后也进入半关闭状态,不过此时服务端的情况是只能发送数据,而不能接收数据。随后服务端也向客户端发送 END_STREAM 帧,表示数据发送完毕,双方进入关闭状态。

如果下次要开启新的流,流 ID 需要自增,直到上限为止,到达上限后开一个新的 TCP 连接重头开始计数。由于流 ID 字段长度为 4 个字节,最高位又被保留,因此范围是 0 ~ 2 的 31 次方,大约 21 亿个。

Released under the MIT License.