这篇博文讲述笔者在阅读《HTTP/2基础教程》这本书时的读书笔记,同时也会讲述一些个人见解。
这章主要介绍通过简单的方法启动和运行一个h2服务器。主要以下两步
1、获取并安装一个支持h2的Web服务器
2、下载并安装一张TLS证书,让浏览器和服务器通过h2连接。
最简单的方法就是对于步骤1用nghttpd,对于步骤2直接用openssl
生成自签名证书。具体过程不做赘述。
本章主要介绍:
1、HTTP关注的性能指标,比如延迟、首字节时间、内容下载时间等
2、HTTP/1的问题
3、Web性能优化的手段,即为满足关注的性能指标而优化HTTP/1的不足所采用的手段
队头阻塞
,即使HTTP/1优化过程中有了并行连接和管道化连接的手段,但再多的连接有可能存在队头阻塞的问题。慢启动
过程,另外还有延迟确认、Nagle算法等,每个HTTP连接都会经历慢启动的过程,从而没有最大程度地利用带宽。在本书出版的时候,大约80%的浏览器可以在一定程度上支持h2,常见的Chrome、Firefox、Edge、safari等,这章介绍了网站从h1向h2迁移过程中的注意事项,不必在意。
作为一个网络开发者,主要关注的其实也就是这一章。
这章全面探讨了HTTP/2的低层工作原理,深入到数据层传输的帧及其通信方式。这也是本文阐述的重点。书中对连接、流、消息、帧的介绍比较混乱,笔者将统一介绍这些关键内容。
h2的关键特性之一就是在HTTP与TCP之间增加一个二进制分帧层。分帧层这三个字乍看上难以理解,我是这样理解的:分、即分开、划分,帧指的是H2中的帧,层与网络层应用层的层对应。即,在网络层和应用层之间又抽象出了一个层用来划分HTTP的帧,叫做分帧层。 不 知 道 这 样 理 解 对 不 对 , 欢 迎 斧 正 \color{red}{}{不知道这样理解对不对,欢迎斧正} 不知道这样理解对不对,欢迎斧正
例如:原来的HTTP协议中一个HTTP响应包含一到多个TCP段,而h2中的一个HTTP响应可能包含1到多个HTTP帧,一般一个HTTP帧对应一个TCP段。
主要特性:
连接:即传统意义上客户端与服务端的一个TCP连接,源IP,源端口,目的IP,目的端口四个元素定义一条独一无二的链接。
流:一个连接上可以有多个流,一个流通常对应一次HTTP请求和响应。
消息:一个流上可以有多个消息,消息分为两种,请求消息和响应消息。
帧:一个消息由一个或多个帧组成,每个帧对应一个流,一个流里有多个帧。
1、各个流之间是相互独立的,没有影响
2、其中一个流阻塞,不会影响其他流
3、流可以有优先级,约定优先级高的流优先处理
1、流是一个逻辑上的概念
2、实际上连接里只看到帧,每个帧都有一个流ID用于标识该帧所属的流。
如图所示,11,13,15,17,19这样的数字就是流ID,每一个帧都拥有一个流ID标识该帧所属的流。
在这几个流中,11,13,15,17,19的请求是按顺序发送的,但是相应确是乱序的,说明各个流之间互不影响。
15这个流中,包含一个请求帧,两个响应帧。
名称 | 长度 | 描述 |
---|---|---|
Length | 3字节 | 表示帧的负载的长度,取值范围为0~ 2 24 2^{24} 224-1字节。请注意, 2 14 2^{14} 214字节是默认的最大帧大小,如果需要更大的帧,必须在SETTINGS帧中设置。 |
Type | 1字节 | 当前帧的类型,见下表 |
Flags | 1字节 | 具体帧类型的标识 |
R | 1位 | 保留位,不要设置,否则会带来严重后果 |
Stream Identifier | 31位 | 每个流的唯一ID |
Frame Payload | 长度可变 | 真实的帧内容,长度是在Length字段中设置的 |
帧类型
名称 | ID | 描述 |
---|---|---|
DATA | 0X0 | 传输流的核心内容 |
HEADERS | 0x1 | 包含HTTP首部,和可选的优先级参数 |
PRIORITY | 0x2 | 指示或者更改流的优先级和依赖 |
RST_STREAM | 0x3 | 允许一端停止流(通常是由于错误导致的) |
SETTINGS | 0x4 | 协商连接级参数 |
PUSH_PROMISE | 0x5 | 提示客户端,服务端要推送些东西 |
PING | 0x6 | 测试连接性和往返时延 |
GOAWAY | 0x7 | 告诉另一端,目前端已经结束 |
WINDOW_UPDATE | 0x8 | 协商一端将要接收多少字节(用于流量控制) |
CONTINUATION | 0x9 | 用于扩展HEADER数据块 |
h2提供了客户端和服务端调整传输速度的能力。WINDOW_UPDATE帧用来指示流量控制信息。WINDOW_UPDATE发送方告诉接收方,发送方能够接收多少字节。当发送方接收并消费接收到的数据时,发送方再发出一个WINDOW——UPDATE帧以支持更新后的处理字节的能力。
例如:
在流建立的时候,窗口的默认大小都是65535( 2 16 − 1 2^{16}-1 216−1)字节。假设客户端A支持该默认值,他的另一端(B)发送了10000字节,B也会关注窗口大小(现在有55535字节了)。现在A花时间处理了5000字节,还剩下5000字节,然后它会发送一个WINDOW_UPDATE帧,说明它现在的窗口大小是60535字节。B收到这个帧后,开始发送了一个大文件(比如4GB大小)。在这个场景下,在B等A准备好接收更多的数据之前,B能发送的数据量就是当前窗口的大小,即60535字节。通过这种方式,A可以控制B发送数据的最大速率。
H2的优先级通过HEADERS帧和PRIORITY帧实现,客户端可以明确地和服务端沟通它需要什么,以及它需要这些资源的顺序。这是通过声明依赖关系树和树立的相对权重实现的。
* index.html
- style.css
- critical.js
- less_critical.js(weight 20)
- photo.jpg(weight 8)
- hearder.jpg(weight 8)
- ad.js(weight 4)
例如:可以利用依赖关系和权重,实现上述的依赖关系树,不同层级的资源优先级不同。对于同一层级的资源,权重不同,意味着重要程度不同。服务端将会参考依赖关系树中的关系对资源进行处理。
但是需要注意的是,依赖关系树和权重也只是客户端的建议,具体做什么以及如何处理优先级,还是得听服务器的。处理优先级的智能水平,可能会是决定各种支持h2的Web服务器性能优劣的因素。
说了这么多,让我们来看看HEADERS帧和PRIORITY帧是如何实现优先级的。
命长 | 长度 | 描述 |
---|---|---|
Pad Length(填充长度) | 1字节 | 填充字节的长度;帧首部的PADDED标识设置为1时才会有该字段 |
E | 1位 | 标识流依赖是否为专用;只有设置了PRIORITY标识才会有该字段 |
Stream Dependency(流依赖) | 31位 | 标识当前流所依赖的流,如果有的话,只有设置了PRIORITY标识才会有该字段 |
Weight(权重) | 1字节 | 当前流的相对权重;只有设置了PRIORITY标识才会有该字段 |
Header Block Fragment()首部块片段 | 长度可变 | 消息的首部 |
Padding(填充数据) | 长度可变 | 长度为Pad Length字段的值,所有的字节被设置为0,据说是为了安全 |
提升单个对象性能的最佳方式,就是在它被用到之前就放到浏览器的缓存里面。这正是HTTP/2的服务端推送的目的。推送是服务器能够主动将对象发送给客户端,这可能是因为它知道客户端不久将用到该对象。
如果服务端要推送一个对象,会构造一个PUSH_PROMISE帧。这个帧有很多重要属性:
首部压缩算法HPACK非常的复杂。但简单来看就类似查表法,服务端和客户端各保存一张表,当客户端发送请求时,如果首部已经在表中存在,那就只需要发送一个首部索引,服务端拿到索引之后再查表从而得到真正的首部。
参考:
【1】Hypertext Transfer Protocol Version 2 (HTTP/2 )