一、现状
1. 网络性能
现在网络优化的瓶颈是什么?你可能会说,带宽。也许在 2014 年前,决定性能的关键是带宽,但是在今天以及以后,瓶颈都不会是带宽,而是延迟;
从图中可以看出,随着带宽的增长,页面加载时间(PLT Page Load Time)在 1Mbps 到 3Mbps 的区间得到了很大的改善,但是再提高带宽,带来的提升就很小了,属于非线性改善;反观延迟,延迟(这里是指多个 RTT 时间相加的总和)的改善对于页面加载时间是属于线性改善;
HTTP/1.1 TCP 连接是需要三次握手的,同时,多个 TCP 连接也会给服务器带来资源的消耗,在 HTTP/1.1 中,每个请求回复都是一次 TCP 连接(未开启 Keep-Alive 的情况下),并且,同时传输多个资源时,会有队首阻塞的问题,造成网络资源无法有效利用;
2. 安全
对于大多数人来说,下图的情况几乎都有遇到过(电脑或手机里)。万恶的运营商或者网络接入 WiFi 提供商劫持我们的网络,修改网络的内容,给我们带来了很大的困扰;
二、HTTP/2.0
现在,HTTP/2.0 出现了。其实 HTTP/2.0 是支持 Clear Text 版和 Over TLS 版,由于现有支持 HTTP/2.0 的浏览器都是实现的 Over TLS 版,故本文的 HTTP/2.0 都是讲的是 HTTPS 版 HTTP/2.0;
1. Clear Text 版:
客户端向服务端请求(假设此时 scheme 是 HTTP),带有以下头:
Upgrade: h2c
HTTP2-Settings
服务器端返回:
101 状态码,转换协议;
Connection: Upgrade
Upgrade: h2c 或者 200/404
2. HTTP/2.0 Over TLS 版:
客户端向服务器端请求
TLS + ALPN(Application Layer Protocol Negotiation)/NPN
服务器端返回:
TLS 握手 并返回支持的 HTTP 协议;
a. TLS 握手详细过程
b. ALPN 协商过程
参考 TLS 握手过程图,下面是增加 ALPN 协商的具体过程:
客户端添加一个 ProtocolNameList 字段,包含支持的 HTTP 协议到 ClientHello 消息中;
服务器端查看 ProtocolNameList 字段后通过 ServerHello 消息返回 ProtocolName 字段,表明被选定的协议;
通过实现 ALPN,不再需要单独请求一次服务器带上 Upgrade: h2c;
c. False Start
通常情况下,使用 ALPN 会搭配使用 False Start,客户端在完成 TLS 握手前提前发送加密后的应用数据,将两次 RTT TLS 握手减少为一次;不过需要同时支持 ALPN(NPN 已经很少用啦)和前向安全性;
d. HSTS
HTTP Strict Transport Security(简称为 HSTS)是一个安全功能,告诉浏览器只能通过 HTTPS 访问当前资源,禁止 HTTP 方式。
如果用户输入域名www.qq.com,浏览器首先会去请求http://www.qq.com,请求过程是明文非加密的,此时容易被中间人攻击,让网路恶意中间商直接接触到用户信息;而 HSTS 是用户请求时,服务器告诉客户端,下次来请求直接请求 https://,而不要再请求服务器来跳转到 https;
同时,开启 HSTS 后,如果证书认证不通过(比如遭到中间人攻击),浏览器此时强制无法打开该网站;
3. 名词解释
流(Stream):一个 Stream 是包含一条或多条信息,ID 和优先级的双向通道;
消息(Message):消息由帧组成;
帧(Frame):帧有不同的类型,并且是混合的。他们通过 stream id 被重新组装进消息中;
4. 概念解释
a. 二进制帧
HTTP2 的二进制帧是 9 字节(72 bit)
长度:24bit,也就是理论上可以携带 2^24 字节的数据。但通常由于 SETTINGS_MAX_FRAME_SIZE 的设置,不能发送超过 2^14(16384)字节的数据;
类型:8bit,决定了该帧的类型;
DATA : 数据帧
HEADERS : 头部帧
PRIORITY : 设置流的优先级
RST_STREAM : 终止流
SETTINGS : 设置连接参数
PUSH_PROMISE : 服务器推送模拟请求帧
PING : 用来计算 RTT 时间和看是否服务器挂了的 Stream
GOAWAY : 告诉对方停止再向当前连接创建 stream
WINDOW_UPDATE : 流量控制
保留字段:1bit,一般为 0;
Stream ID:31bit,Stream 标识,理论上可以有 2147483648,超过这么多 stream 怎么办呢?
如果是客户端无法再创建新的 stream id,可以直接创建新的 TCP 连接,stream id 被重置;
如果是服务器端无法再创建新的 stream id,服务器将会给客户端发一个 GOAWAY 帧,客户端无法再向该服务器创建 stream,不得不新建 TCP 连接;
5. 新特性
多路复用
头部压缩
资源优先级/依赖关系
流量控制
Server Push(客户端请求一个路径时,服务器推送一个资源给客户端)
a. 多路服用
HTTP/2.0 中,数据在发送端被切分为更小的数据帧用以高效利用链接;
HTTP 1.1 时代,再不开启 Keep-alive 的情况下,每一个请求会占用一个 TCP 连接,而 HTTP/2 将请求和响应消息拆分为各自独立的帧,交错的发送,然后再在接收端重新装配组合。有什么好处呢?
交错的多个请求/响应之间互现不会被阻塞
HTTP/1.1 时代的 Keep-alive 也是保持同一个 TCP 连接,但是由于请求/接收有先后,后面的请求资源会被前面的资源阻塞(没收到响应时不会发新的请求),如下图最左和最右边所示,即便是相比 HTTP 管道,优化也是巨大的:
减少了不必要的延时,改善了网路的利用率(多路复用和资源优先级/依赖关系搭配使用,使得页面重度依赖的资源优先传输);
b. 头部压缩
HTTP/2.0 使用 HPACK 来给头部压缩;
值通过霍夫曼编码;
之前发送的值都被索引起来,之后使用时发现之前发送过该 Header 字段,并且值相同,就会沿用之前的索引来指代那个 Header 值;
Cookies:在 HTTP/2.0 中,Cookie 也将会变为键值对索引起来,而不是一长串字符串;
可以看看我们组 dream 同学的 HTTP/2.0 之特性科普篇——头部压缩,里面有截图部分的数据讲述压缩后的效果;
这里需要讲解一下伪头部字段:
请求:
:authority
:method
:path
:scheme
响应:
:status
所有的伪头部字段都是在所有 Header 的前部;
c. 资源优先级/依赖关系
资源优先级/依赖关系通过 stream 权重和 dependency 来设置;
通过上图可以看到,有一列是叫作 Priority,初始设置是根据 Content-type 来设置优先级的,比如 HTML 是 Highest,CSS 是 High,然后 JS 是 Medium;
Stream 权重值可以设置为 1 到 256 之间;
Stream 可以明确的表示依赖关系;
注意,一定要理解权重和依赖,权重值和依赖关系是作为带宽资源/服务器/客户端处理资源的建议值,但并不能保证他们有特定的传输顺序。让我们来看一张 HTTP/2.0 的依赖关系和权重图:
HTTP/2.0 中的 stream 都默认是依赖于一个根 stream(其实不存在)。权重值是针对同级来计算的,不同级是不用来计算的;
d. 流量控制
与 TCP 的流量控制类似,不过 HTTP/2.0 的流量控制可以到具体帧,而 TCP 是 TCP 连接层面上的。注意:流量控制目前只对 DATA 帧有效!流量控制的算法没有具体要求使用哪一种,但是大概实现的功能是这样的:
两端收发保有一个流量控制(window)窗口;
发送端每发送一个 DATA 帧,就把窗口的大小递减,递减量为这个帧的大小,要是窗口大小小于该帧的大小,那么这个帧就必须被拆分。如果窗口值等于 0,就不能发送任何帧。流量控制的初始默认窗口值大小为 65535 字节(理论上可以设置 2^31-1 字节也就是 2147483647 字节大小的窗口值)。
接收端可以通过发送 WINDOW_UPDATE 帧给发送端,发送端以帧内指定的窗口大小增量加到窗口大小限制上。
e. Server Push
Server Push 的资源同样需要遵守同源策略,通过:authority 来判断;
如 Demo 里所示,如果在服务器端设置当请求 Path/examples/dashboard 时就推送/examples/dashboard/d3.js,现在我们来看抓包:
说明:
当客户端请求服务器时(此时的请求路径已经设置好推送),服务器发回一个 PUSH_PROMISE 和两个 HEADERS Frame,从 Stream Identifier 可以看出,第一个 HEADERS 的 Stream ID 是 1,也就是复用请求的 Stream 来返回(这是 HTML 文件的返回响应 Header)。第二个 HEADERS 就是推送文件的响应 Header。
根据定义,由客户端初始化发起的 Stream 的标识符是奇数,由服务器端初始化发起的 Stream 是偶数,图中可以体现;
那么 Stream 1 和 Stream 2 的顺序如何保证呢?说明文档里有这样一句话:
Pushed streams initially depend on their associated stream.
也就是说,服务器将要推送的资源依赖于触发推送的请求,根据 Stream 依赖的功能,只有被依赖的 stream 加载完后才会去加载接下来的 stream;
Server Push 有什么好处呢:
推送的资源可以被客户端缓存;
推送的资源可以被不同的页面复用;
推送资源也是支持多路复用的;
推送资源可以被客户端拒绝掉(客户端接收到 PUSH_PROMISE 后,可以选择发送 RST_PROMISE 来拒绝接收,告诉服务器端不要再发送了,当然,此时可能已经有部分内容已经发送过来了);
同时,Server Push 配合流量控制,可以实现很多很神奇的功能,这里卖个关子,然后会在下一篇讲解 :)
本文转载自公众号小时光茶舍(ID:gh_7322a0f167b5)。
原文链接:
https://mp.weixin.qq.com/s/Y1x0GUkfRbWEz6YsJrMTAQ
评论