http2 简介

本文首发于 https://jaychen.cccss

做者:jaychenhtml

写一点东西关于 http2 的东西。前端

http2 的前身是由 google 领导开发的 SPDY,后来 google 把整个成果交给 IETF,IETF 把 SPDY 标准化以后变成 http2。google 也很大方的废弃掉 SPDY,转向支持 http2。http2 是彻底兼容 http/1.x 的,在此基础上添加了 4 个主要新特性:算法

  • 二进制分帧
  • 头部压缩
  • 服务端推送
  • 多路复用
  • 优化手段

下面主要讲下这 4 个特性。chrome

二进制分帧

http/1.x 是一个文本协议,而 http2 是一个不折不扣的二进制协议,这也是 http2 能够折腾出那么多新花样的缘由。http2 的二进制协议被称之为二进制分帧浏览器

http2 协议的格式为帧,相似 TCP 中的数据报文。bash

+--------------------------------------------------------------+   ^
|                                                              |   |
|                   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 中。服务器

  • Type 字段用来表示该帧中的 Frame Payload 保存的是 header 数据仍是 body 数据。除了用于标识 header/body,还有一些额外的 Frame Type。
  • Length 字段用来表示 Frame Payload 数据大小。
  • Frame Payload 用来保存 header 或者 body 的数据。

**Stream Identifier 用来标识该 frame 属于哪一个 stream。**这句话可能感受略突兀,这里要明白 Stream Identifier 的做用,须要引出 http2 的第二个特性『多路复用』。并发

多路复用

在 http/1.x 状况下,每一个 http 请求都会创建一个 TCP 链接,这就意味着每一个请求都须要进行三次握手。这样子就会浪费比较多的时间和资源,这点在 http/1.x 的状况下是没有办法避免的。而且浏览器会限制同一个域名下并发请求的个数。因此,在 http/1.x 的状况下,一个常见的优化手段是把静态资源分布到不一样域名下,以此来突破浏览器并发数的限制。性能

在 http2 的状况下,全部的请求都会共用一个 TCP 链接,这个能够说是 http2 杀手级的特性了。 :punch: 由于这点,许多在 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 次请求:

  • 发起 1 次请求 index.html 页面
  • 解析 index.html 页面发现 style.css 和 something.png 资源,发起 2 次请求获取资源。

若是服务端配置了服务端推送以后,那么状况变成下面的样子:

  • 浏览器请求 index.html。
  • 服务器发现浏览器请求的 index.html 中包含 style.css 和 something.png 资源,因而直接 index.html, style.css, something.png 三个资源都返回给浏览器。

这样,服务端和浏览器只须要进行一次通讯,就能够获取到所有资源。

http/1.x 转 http2

http2 的目的就是为了优化 http/1.x 的一些性能问题,因此当 http2 到来以后,不少针对 http/1.x 的优化手段已经无论用。而使用 http2 咱们又应该注意一些什么问题?

https

https 和 http2 的恩怨颇有趣。google 在开发 SPDY 的时候是强制使用 https 的,按照道理基于 SPDY 的 http2 也应该是强制 https 的,可是因为社区的阻碍 http2 能够不使用 https 协议。可是 chrome 和 firefox 都表示只会开发基于 https 的 http2,因此基本意味着使用 http2 的前提是必须是 https。

没必要要的优化

在 http/1.x 的时代,为了减小浏览器的请求数/提升浏览器的并发数,一般会使用以下的手段来进行优化:

  • 域名分片:把静态资源分布在不一样的域名下,以突破浏览器对统一域名并发数的限制。(在多路复用中提到)
  • 合并文件:前端一般会把多个小文件合并成一个大文件,这样浏览器只要进行一次请求就能够获取资源。可是这样作有一个缺陷就是:若是只是改动了文件的一小部份内容,就要从新发送所有内容。

以上的优化手段,在 http2 的状况下,就显得没必要要了。