26 HTTP/2 内核
一篇还不错的博文:深入理解http2.0协议,看这篇就够了!
-
总结
1、HTTP/2 必须先发送一个 连接前言 字符串,然后才能建立正式连接
2、HTTP/2 废除了起始行,统一使用头字段,在两端维护字段
Key-Value
的索引表,使用HPACK
算法压缩头部 3、HTTP/2 把报文切分为多种类型的 二进制帧,报头里最重要的字段是 流标识符,标记帧属于哪个流4、流是 HTTP/2 虚拟的概念,流是帧的双向传输序列,相当于 HTTP/1 里的一次 请求 - 应答
5、在一个 HTTP/2 连接上可以并发多个流,也就是多个 请求 - 响应 报文,这就是 多路复用
连接前言
-
作用
TLS 握手成功之后,客户端必须要发送一个 连接前言 Magic(connection preface),用来确认建立 HTTP/2 连接
-
内容
HTTP/1 请求报文,使用纯文本的 ASCII 码格式,请求方法是特别注册的一个关键字
PRI
,全文只有 24 个字节:PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
头部压缩
在请求发送前,必须要用 HPACK
算法来压缩头部数据
HPACK
算法核心
需要客户端和服务器各自维护一份 **索引表**,压缩和解压缩就是 **查表和更新表** 的操作
-
伪头字段
HTTP/2 废除了原有的起始行概念,把起始行里面的 请求方法、URI、状态码 等统一转换成了伪头字段(比真正的头字段在名字多前加一个
:
):authority
:method
:status
,分别表示的是 域名、请求方法和状态码
-
静态表
HTTP 报文头:
Key-Value
形式的字段HTTP/2 为一些最常用的头字段定义了一个只读的静态表
:method=GET
使用索引值 2 表示,:path=/index.html
使用索引值 5 表示 -
动态表
和静态表结构相同,但会在 编码解码的时候随时更新
比如说,第一次发送请求时的
user-agent
字段长是一百多个字节,用哈夫曼压缩编码发送之后,客户端和服务器都更新自己的动态表,添加一个新的索引号65
。那么下一次发送的时候就不用再重复发那么多字节了,只要用一个字节发送编号就好
报文拆解成二进制帧
帧结构:只有 9 字节
解析细节:
1、帧长度:3 个字节
–> 抓包示例中为 00010a
,表示数据长度是 266 字节
2、帧类型:分成 数据帧
和 控制帧
两类,可扩展,例如,HEADERS
帧和 DATA
帧属于数据帧,存放的是 HTTP 报文,而 SETTINGS
、PING
、PRIORITY
等则是用来管理流的控制帧
–> 抓包示例中为 01
,表示 HEADERS
帧,负载(payload)里面存放的是被 HPACK 算法压缩的头部信息
3、标志位:可以保存 8 个标志位,携带简单的控制信息,例如,END_HEADERS
表示头数据结束,END_STREAM
表示单方向数据发送结束
–> 抓包示例中为25
,转换成二进制 11001
有 3 个位被置 1,PRIORITY
表示设置了流的优先级,END_HEADERS
表示这一个帧就是完整的头数据,END_STREAM
表示单方向数据发送结束,后续再不会有数据帧
4、流标识符:代表了 帧所属的流,接收方使用它就可以从乱序的帧里识别出具有相同流 ID 的帧序列,按顺序组装起来就实现了虚拟的流
–> 抓包示例中为 01
,表示这是客户端发起的第一个流,后面的响应数据帧也会是这个 ID,也就是说在 stream[1]
里完成这个请求响应
## 流与多路复用
流是二进制帧的双向传输序列,如下是三个流 0、1、3
流有哪些特点?
1、流是 可并发 的,一个 HTTP/2 连接上可以同时发出多个流传输数据,也就是并发多请求,实现 多路复用
2、客户端和服务器都可以创建流,双方互不干扰
3、流是双向的,一个流里面客户端和服务器都可以发送或接收数据帧,服务端推送(遵守同源策略)
4、流之间没有固定关系,流之间彼此独立,但流内部的帧是有严格顺序的
5、流可以设置优先级,让服务器优先处理,比如先传 HTML -> CSS -> js -> 传图片,优化用户体验
6、流 ID 不能重用,只能顺序递增,客户端 发起的 ID 是 奇数,服务器端 发起的 ID 是偶数
7、在流上发送 RST_STREAM
帧可以 随时终止流,取消接收或发送
8、第 0 号流 比较特殊,不能关闭,也不能发送数据帧,只能发送 控制帧,用于流量控制
注意,根据上述特性可以推论:
1、HTTP/2 在一个连接上使用多个流收发数据,那么它本身 默认就会是长连接
2、下载大文件的时候想取消接收,在 HTTP/1 里只能断开 TCP 连接,重新三次握手,成本很高,而在 HTTP/2 里就可以简单地发送一个 RST_STREAM
中断流,而 长连接会继续保持
3、因为客户端和服务器两端都可以创建流,而流 ID 有奇数偶数和上限的区分,所以大多数的流 ID 都会是奇数
4、ID 用完的话,客户端可以发送控制帧 GOAWAY
,真正关闭 TCP 连接
流状态转换
根据 帧的标志位 实现 流状态转换
下一次再发请求就要 开一个新流(而不是新连接),流 ID 不断增加,直到到达上限,发送 GOAWAY
帧开一个新的 TCP 连接,流 ID 就又可以重头计数。