本文首发于 https://jaychen.cc
做者 :jaychen
写一点东西关于 http2 的东西。css
http2 的前身是由 google 领导开发的 SPDY,后来 google 把整个成果交给 IETF,IETF 把 SPDY 标准化以后变成 http2。google 也很大方的废弃掉 SPDY,转向支持 http2。http2 是彻底兼容 http/1.x 的,在此基础上添加了 4 个主要新特性:html
下面主要讲下这 4 个特性。前端
http/1.x 是一个文本协议,而 http2 是一个不折不扣的二进制协议,这也是 http2 能够折腾出那么多新花样的缘由。http2 的二进制协议被称之为二进制分帧。算法
http2 协议的格式为帧,相似 TCP 中的数据报文。chrome
+--------------------------------------------------------------+ ^ | | | | Length (24) | | | | | | | | +----------------------+---------------------------------------+ | | | | + | | | | Type (8) | Flag (8) | Frame Header | | | + +----+-----------------+---------------------------------------+ | | | | | | | | | | R | Stream Identifier (31) | | | | | v +----+---------------------------------------------------------+ | | | Frame Payload | | | +--------------------------------------------------------------+
帧由 Frame Header 和 Frame Payload 组成。以前在 http/1.x 中的 header 和 body 都放在 Frame Payload 中。浏览器
Stream Identifier 用来标识该 frame 属于哪一个 stream。这句话可能感受略突兀,这里要明白 Stream Identifier 的做用,须要引出 http2 的第二个特性『多路复用』。服务器
在 http/1.x 状况下,每一个 http 请求都会创建一个 TCP 链接,这就意味着每一个请求都须要进行三次握手。这样子就会浪费比较多的时间和资源,这点在 http/1.x 的状况下是没有办法避免的。而且浏览器会限制同一个域名下并发请求的个数。因此,在 http/1.x 的状况下,一个常见的优化手段是把静态资源分布到不一样域名下,以此来突破浏览器并发数的限制。并发
在 http2 的状况下,全部的请求都会共用一个 TCP 链接,这个能够说是 http2 杀手级的特性了。 由于这点,许多在 http/1.x 时代的优化手段均可以退休了。可是这里也出现了一个问题,全部的请求都共用一个 TCP 链接,那么客户端/服务端怎么知道某一帧(别忘记上面说了 http2 是的基本单位是帧)的数据属于哪一个请求呢?性能
上面的 Stream Identifier 就是用来标识该帧属于哪一个请求的。优化
当客户端同时向服务端发起多个请求,那么这些请求会被分解成一一个的帧,每一个帧都会在一个 TCP 链路中无序的传输,同一个请求的帧的 Stream Identifier 都是同样的。当帧到达服务端以后,就能够根据 Stream Identifier 来从新组合获得完整的请求。
在 http/1.x 协议中,每次请求都会携带 header 数据,而相似 User-Agent, Accept-Language 等信息在每次请求过程当中几乎是不变的,那么这些信息在每次请求过程当中就变成了浪费。因此, http2 中提出了一个 HPACK 的压缩方式,用于减小 http header 在每次请求中消耗的流量。
HPACK 压缩的原理以下 :
客户端和服务端共同维护一个『静态字典』,字典中每行 3 列,相似下表
index | header name | header value |
---|---|---|
2 | :method | GET |
3 | :method | POST |
当请求的 header 头部中包含 :mehtod:GET
,客户端在发送请求的时候,会直接发送静态字段中对应的 index 值,在这里也就是 2。服务端在接受到请求的时候,去寻找静态字典中 index = 2 对应的 header name 和 header value,就明白了客户端发起了一个 GET 请求。
客户端和服务端必须维护一套同样的静态字典,这里给出了完整的静态字典,客户端和服务端都会遵照这套静态字典。
你会发现静态字典中有些 header value 没有值。这是由于有些 header 字段的值是不定的,好比 User-Agent 字段,因此标准中没有定下 header value 的值。
那么若是碰到在静态字典中 header value 没有的值,HPEACK 算法会采起下面的方式:
假设 http 请求的 header 中包含了 User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
,那么 HPACK 会对 User-Agent
的值进行哈夫曼编码,而后在静态字典中找到 User-Agent
的 index 为 58,那么客户端会把 User-Agent
的 index 值和 User-Agent
值对应的哈夫曼编码值发送给服务端。
User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36 会被转换陈下面的 kv 值发送给服务端: 58 : Huffman('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36')
服务端收到请求以后,把 User-Agent
和哈夫曼编码值追加到静态字典后面,这些追加的行称之为『动态字典』。
index | header name | header value |
---|---|---|
2 | :method | GET |
3 | :method | POST |
... | .... | ..... |
62 | User-Agent | Huffman('header value') |
客户端在发送请求的时候,也会把该行添加到本身维护的静态字典表后面,这样子客户端和服务端维护的字典表就会保持一致。以后的请求客户端若是须要携带 User-Agent
字段,只要发送 62 便可。
http2 中状况就彻底不同了,全部的请求都是在一个 TCP 链接中完成的。
服务端推送指的是服务端主动向客户端推送数据。
举个例子,index.html 有以下代码
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="style.css"> </head> <body> <h1>hello world</h1> <img src="something.png"> </body> </html>
那么正常状况下,为了展现页面须要发 3 次请求:
若是服务端配置了服务端推送以后,那么状况变成下面的样子:
这样,服务端和浏览器只须要进行一次通讯,就能够获取到所有资源。
http2 的目的就是为了优化 http/1.x 的一些性能问题,因此当 http2 到来以后,不少针对 http/1.x 的优化手段已经无论用。而使用 http2 咱们又应该注意一些什么问题?
https 和 http2 的恩怨颇有趣。google 在开发 SPDY 的时候是强制使用 https 的,按照道理基于 SPDY 的 http2 也应该是强制 https 的,可是因为社区的阻碍 http2 能够不使用 https 协议。可是 chrome 和 firefox 都表示只会开发基于 https 的 http2,因此基本意味着使用 http2 的前提是必须是 https。
在 http/1.x 的时代,为了减小浏览器的请求数/提升浏览器的并发数,一般会使用以下的手段来进行优化:
以上的优化手段,在 http2 的状况下,就显得没必要要了。