http协议之http2


在了解HTTP/2的过程中,查阅了很多资料,发现这篇外国的文章很好,翻译了一些过来,加入自己的一点理解。
HTTP/2是2015年新发布的HTTP协议,旨在提高了资源访问效率。HTTP/2也被称为HTTP 2.0,相对于HTTP 1.1新增了多路复用、压缩HTTP头、划分请求优先级、服务端推送等特性,解决了在HTTP 1.1中一直存在的问题,优化了请求性能,同时兼容了HTTP 1.1的语义。是现行HTTP协议(HTTP/1.1)的兼容升级版本,HTTP方法、状态码、语义与HTTP/1.1一样。HTTP/2 相比于 HTTP/1.1,可以说是大幅度提高了网页的性能,只需要升级到该协议就可以减少很多之前需要做的性能优化工作。它基于SPDY,专注于性能,最大的一个目标是在用户和网站间只用一个连接(connection)。
新特性
二进制传输、Header压缩、多路复用、服务器推送。
HTTP/1.1的缺陷主要包含连接无法复用、队头阻塞、协议开销大和安全因素,而HTTP/2 通过多路复用、二进制流、Header 压缩等技术,极大地提高了性能,弥补了这些缺陷。HTTP/2通过支持请求与响应的多路复用来减少延迟,通过压缩HTTP首部字段将协议开销降至最低,同时增加对请求优先级和服务器端推送的支持。目前百度云加速已完全支持HTTP/2,免费版和收费版默认开启此功能,无需用户进行任何配置操作,HTTP/2将取代HTTP/1.1被广泛采用。
主要从以下两个方面来优化:
1、头部压缩
HTTP1.1主要是对Body进行压缩,而头部却没有压缩。HTTP2通过HPACK算法对头部进行压缩,减少了传输时间
2、队头阻塞
HTTP1.1使用的是TCP协议,并且为了节省资源,采用了长连接,长连接引入了队头阻塞的问题。HTTP2引入了流和帧,解决了HTTP层面上的队头阻塞
HTTP2报文结构
1、二进制替换文本
HTTP1.1采用的是文本描述,也就是通过ASCII文本进行传输,采用文本描述的好处是调试程序方便,能够直接看出数据包中的数据情况。而HTTP2使用的二进制来进行传输,用01串来描述数据,调试和查看数据的具体含义就不那么方便了。但是这种二进制传输使的计算机解析方便,体积小,性能高。
举个例子,比如之前需要传递对应的文本值来表示对应的选择,而通过二进制编码就能知道编码对应的选择,一个二进制编码对应着一个文本值。二进制编码肯定比文本占用的空间小。
2、HTTP2将HTTP1中数据包分解为了帧
将HTTP1.1的数据包分为HEADERS帧和DATA帧,属于是化整为零,为的就是多路复用,具体原理下文再说。


HTTP2连接过程
连接前言
当TCP三次握手成功后,客户端需要向服务器发送一个连接前言。这是一个HTTP1.1报文,是一个ASCII文本,里面记录着一个magic字符串,当服务器接收到这个请求后,服务器就知道客户端想要使用HTTP2协议。
头部压缩
确定了HTTP2连接后,就开始准备发送HTTP2请求了。
首先就是发送HEADERS帧,HTTP1.1中对Body进行GZIP等压缩方式,但是对于头部却没有采取任何办法。HTTP2采用了 HPACK 算法来压缩,HPACK 中客户端和服务器都要维护一个索引表。

如上图所示,索引表中维护了头部中Key-Value的对应关系和索引下标。到时候,HTTP2请求中头部不用再发送Key-Value了,直接发送对应的索引下标就可以了。
上述的索引表是一个静态的表,记录着常用的头字段。HTTP的起始行中记录了Method、StatusCode等信息,也被记录到了索引表中。
有的HTTP2头部有着特殊的头部字段,所以HPACK又在静态表的尾部开始维护一个动态记录。当第一次发送索引表中不存在的Key-Value的时候,会使用实际数据发送,客户端和服务器对应的索引表会对其缓存,等到下一次发送的时候,就可以直接发送索引下标了。
所以说,HTTP2通过HPACK算法,将每次发送Key-Value替换成了发送一个下标,极大的减少了HTTP2头部的数据量,传输效率极大的提高了。
二进制帧
HTTP2将之前的head+body的一个数据包分为了HEADER帧和若干个DATA帧格式,如下图所示:

1、帧长度
帧的总长度,应用程序按照长度来读取
2、帧类型
HTTP2中有 数据帧、控制帧等类别,控制帧有着更高的传输优先权
3、标志位
8个bit,可以代表8个状态,比如END_HEADERS代表是否头数据传输结束、END_STREAM表示单方面传输数据结束
4、流标识符
帧属于哪个流,每个HTTP包传输都是一个虚拟流,通过流标识符才能将前后发送的帧重组为一个完整的数据包
5、实际传输的数据
HTTP2交互过程
HTTP2利用帧+流的方式解决了HTTP1中的队头阻塞问题。
HTTP1中每个HTTP包复用一个TCP连接,每次只能等到前一个HTTP响应后才能发送后一个HTTP请求,如果前一个HTTP响应阻塞的话,后面的HTTP请求都会跟着阻塞,这就是队头阻塞问题。
HTTP2也是利用的长连接,多个HTTP请求复用一个TCP连接。但是HTTP2将每个HTTP请求都看作一个流,将HTTP数据包分解为多个帧,包括HEADER帧和DATA帧。每个帧都对应着一个流标识号。
多个HTTP请求之间的发送是乱序的,但是每个HTTP请求中的帧的发送是有序的。
虽然多个HTTP请求之间的发送请求是乱序的,但是可以基于流标识符来进行重组,一个流标识符对应着一个HTTP请求。

流标识符不能复用,是自动递增的,客户端使用的是奇数,服务器使用的偶数。

这就解决了队头阻塞问题。下面是从外文网站转过来,很好地补充了上文。
HTTP/2更简单、高效、强大,它在传输层解决了以前我们HTTP1.x中一直存在的问题,使用它可以优化我们的应用。其首要目标是通过完全的请求,响应多路复用,头部的压缩头部域来减小头部的体积,添加了请求优先级,服务端推送.为了支持这些特性,他需要大量的协议增加头部字段来支持,例如新的流量控制,差错处理,升级机制.而这些是每个web开发者都应该在他们的应用中用到的.
HTTP/2并没有在应用中改变HTTP的语义,而是通过在客户端和服务端传输的数据格式(frame)和传输.它通过在新的二进制帧层控制整个过程以及隐藏复杂性,而这不需要改变原来有的东西就可以实现.
1. 设计和技术目标
HTTP是因特网广泛普及和采纳的应用层协议.它的易于实现性同样有了对应用性能方面的影响.HTTP/1.x 需要开启多个连接来实现并发和减少潜在影响.HTTP/1.x 的头部没有压缩会造成不必要的网络拥塞.HTTP/1.x没有应用资源优先级,导致重要Tcp连接的糟糕使用.
它的好处如下:
HTTP/2 enables a more efficient use of network resources and a reduced perception of latency by introducing header field compression and allowing multiple concurrent exchanges on the same connection… Specifically, it allows interleaving of request and response messages on the same connection and uses an efficient coding for HTTP header fields. It also allows prioritization of requests, letting more important requests complete more quickly, further improving performance.
The resulting protocol is more friendly to the network, because fewer TCP connections can be used in comparison to HTTP/1.x. This means less competition with other flows, and longer-lived connections, which in turn leads to better utilization of available network capacity. Finally, HTTP/2 also enables more efficient processing of messages through use of binary message framing.
HTTP2.0并没有改变之前HTTP的语义,也就是说高层的Api并没有改变,它是在底层通过二进制frame来改变性能的.
2. 二进制帧层
性能提升的核心在于二进制帧层.它指HTTP消息在客户端和服务端如何封装和传输.

这一层指一个设计选择,它在socket接口之间采用一种更好的编码机制,而高层的Api提供给我们的应用。与HTTP1.x的采用的换行符分隔文本不同,HTTP/2 消息被分成很小的消息和frame,然后每个消息和frame用二进制编码。客户端和服务端都采用二进制编码和解码。HTTP/1.x 的客户端不能与只有HTTP/2的服务端通信。幸运的是,我们的应用还没意识到这些改变。客户端和服务端能够很好的处理这些帧。
ASCII 协议能够很容易的看出来和开始使用。然而它们是没有效率的,且很难正确设计:可选的空白,改变终止序列和其他的毛病使得协议很难区别出payload。虽然二进制协议用起来需要做很多工作,但是它们能表现出更好的性能。
3. 流,消息,帧
接下来介绍二进制帧机制来明白数据如何在客户端和服务端交换的。
流:已经建立的连接之间双向流动的字节,它能携带一个至多个消息。
消息:一个完整的帧序列,它映射到逻辑的请求和响应消息。
帧:在HTTP/2通信的最小单元。每个桢包括一个帧头,里面有个很小标志,来区别是属于哪个流。
所有的通信都建立在一个TCP连接上,可以传递大量的双向流通的流。
每个流都有独一无二的标志和优先级。
每个消息都是逻辑上的请求和相应消息。由一个或者多个帧组成。
来自不同流的帧可以通过帧头的标志来关联和组装起来。

这是 HTTP/2 协议提供高性能的基础。
4. 请求和响应的多路复用
在HTTP/1.x中,用户想要多个并行的请求来提高性能,但是这样必须得使用多个TCP连接.这样的操作是属于HTTP/1.x 发送模型的直接序列.它能保证在每次连接中在一个时间点只有一个响应被发送出去.更糟糕的是,它使得队头阻塞和重要TCP连接的低效使用.
在HTTP/2中,新的二进制帧层,解除了这个限制.使得所有的请求和响应多路复用.通过允许客户端和服务端把HTTP消息分解成独立的帧,交错传输,然后在另一端组装.

图12-3显示了在一次连接中的多个流.客户端传输数据帧到服务端(Stream5).服务端传输交错的帧序列(Stream1,Stream3)到客户端.此时,同时存在并行的3个流.
能够把HTTP消息分解成交错的帧,并在另一端组装它们是HTTP/2中一个非常重要的提高.事实上,它引起了一种波浪效应使得web技术的全栈在性能上有很大的提升.它有以下作用:
交错的多个并行的请求或者,而不需要阻塞.
使用一个连接传递所有的并行的请求和响应.
移除了HTTP/1.x中没有的必要的解决方法.例如级联文件,域分片.
淘汰没必要的潜在因素来降低页面载入的时间.提升可用网络容积的使用率.
新的二进制帧层解决了HTTP/1.X中头部阻塞的问题.在并行处理和传输的请求和响应不再需要多个连接.这使得我们的应用更简单,快捷和便宜.
5. 流的优先级
为了能方便流的传输顺序,HTTP/2.0提出,使每个流都有一个权重和依赖.
每个流的权重值在1~256之间
每个流可以详细给出对其他流的依赖
流权重和依赖的结合使客户端可以构造和通信一个优先级二叉树来表达它更想得到哪种响应.然后服务端可以按权重分配硬件资源(CPU,内存).

在HTTP/2 ,一个流的依赖可以显式用其他流的标志来表达,如果省略了标志,则说明它的依赖是根流.一般来说,父流应该在它的依赖流之前分配资源,例如D应该是C之前被发送.依赖于同一父节点的应该按照他们的权重分配资源.例如A结点的权重为12,它的兄弟结点B的结点的权重为4.然后按比例分资源,A占12/16,B占4/16.如上面所述,流的依赖和权重提供了一种很好的表达式语言来表达资源的优先级.但是我们应该明白,,流的依赖和权重只是提供了一种传输偏好,而不是说一定是这样的比例.
5.1 每个源一个连接
HTTP/2.0的连接是持久的,每个源仅仅需要一个连接.大部分HTTP的传输是短的,并且突然的.然而TCP连接却适合长期存活的,批量的数据传输.通过利用相同的HTTP/2 连接,既能够充分利用TCP连接,也能减小整体协议的头部.更进一步来说,更少的连接内存的占用以及全连接路径的处理过程.向HTTP/2的转移不仅减少了网络潜在因素,更减少了操作代价.
Tips:减少连接,同时也提高了HTTPS的性能,因为仅需要更少的TLS层的握手.
5.2 流量控制
流量控制是一种机制,用来阻止发送者发送大量的接收者不需要,或者没能力处理的数据.接收者可能会在重负下很繁忙,或者只愿意分配固定的资源给特定的流.例如,客户端可能以高的优先级请求大量的视频数据,然后用户暂停了视频,那么客户端现在想要停止或者减少服务端的传输来避免取和缓存没必要的数据.或者一个代理服务器连接有很快的下流,很慢的上流,同样的也要控制以多大的流速传输数据,从而匹配上流的速度,从而控制资源的使用.
这些需求可能让你想起了TCP流量控制,由于HTTP/2的那些流是在一个TCP的连接上.那么TCP连接不够细粒度,也没能提供应用级的API来控制单个流的传输.为了应对这种情况,HTTP/2提供了一系列的简单修筑块,来允许客户端和服务端实现他们自己的流级别的,连接级别的流量控制.
流量控制是有方向的.对于每个流和连接,每个接收者可以设置它想要窗口大小.
流量控制是基于信用的。每个接收者通告其初始连接和流量控制窗口(以字节为单位),只要发送者发送数据帧并通过接受者发送的WINDOW_UPDATE帧递增,该窗口就会减少。
流量控制不能禁用.当建立HTTP / 2连接时,客户端和服务器交换SETTINGS帧,这些帧设置双向流量控制窗口的大小。流量控制窗口的默认值设置为65,535字节,但接收方可以设置更大的最大窗口大小(2的31次方-1字节),并通过在接收到任何数据时发送WINDOW_UPDATE帧来维护它。
流量控制是逐跳的,而不是端到端的.也就是说,一个中介可以使用它控制资源的使用,从而根据自己的标准和启发式实现资源分配机制.
HTTP/2没有规定用于实现流量控制的任何特定算法。相反,它提供了简单的构建模块并将实现推迟到客户端和服务器,这可以用它来实现自定义策略来调节资源使用和分配,以及实现新的传输功能,这可能有助于提高Web应用程序真实性和感知性。例如,应用程序层流控量制允许浏览器仅提取特定资源的一部分,通过将流量控制窗口降至零来暂停提取,然后稍后恢复。又例如,获取预览或第一次浏览图像,显示图像并允许进行其他高优先级操作取来操作,并在关键资源完成加载后又开始取。
6.服务端推送
HTTP/2的另一个强大的新功能是服务器为单个客户端请求发送多个响应的能力。也就是说,除了对原始请求的响应之外,服务器还可以向客户端推送额外的资源(图12-5),而不需要客户端明确请求每一个资源!

HTTP/2脱离了严格的请求 - 响应语义,并支持一对多和服务器启动的推送工作流程,在浏览器内部和外部打开全新的交互可能性。这是一个启动功能,对于我们如何考虑协议以及在何处以及如何使用协议,都会产生重要的长期影响。
为什么我们需要在浏览器中使用这种机制?一个典型的Web应用程序由几十个资源组成,所有这些资源都是客户端通过检查服务器提供的文档发现的。因此,为什么不消除额外的延迟并让服务器提前推送相关资源?服务器已经知道客户端需要哪些资源;这是服务器推动。事实上,如果您曾经通过数据URI将CSS,JavaScript或任何其他资产内联到一起(请参阅资源内联),那么您已经有了服务器推送的实践经验!通过手动将资源内联到文档中,实际上,我们将该资源推送到客户端,而无需等待客户端请求。通过HTTP/2,我们可以获得相同的结果,但是具有额外的性能优势:
推送的资源可以由客户端缓存
推送的资源可以在不同的页面上重复使用
推送的资源可以与其他资源一起复用
推送的资源可以由服务器优先
推送的资源可以被客户拒绝
每个推送的资源都是一个流,与内联资源不同,它允许客户端对其进行单独复用,优先化和处理。由浏览器执行的唯一安全限制是推送资源必须遵守同源策略:服务器必须对提供的内容具有权限。
7. 头部压缩
每个HTTP传输都包含一组描述传输资源及其属性的标题。在HTTP/1.x中,此元数据始终以纯文本形式发送,并且每次传输的开销都会在任何位置增加500-800字节,如果使用HTTP Cookie,则会增加数千字节。为了减少这种开销并提高性能,HTTP/2使用两种简单但强大的技术使用HPACK压缩格式(要了解这个算法可以参考这篇文章)来压缩请求和响应头元数据:
它允许通过静态霍夫曼编码对传输的头部字段进行编码,从而减少它们各自的传输大小。
它要求客户端和服务器都维护和更新先前看到的标题字段的索引列表(即,建立共享压缩上下文),然后将其用作参考以高效编码先前传输的值。
霍夫曼编码允许单个值在传输时被压缩,并且先前传输值的索引列表允许我们通过传输索引值来编码重复值(图12-6),索引值可用于有效地查找和重建完整头部键和值。

作为进一步优化,HPACK压缩上下文由静态和动态表组成:静态表在规范中定义,并提供所有连接可能使用的常见HTTP头字段的列表(例如,有效头名称);动态表最初是空的,并基于特定连接内的交换值进行更新。因此,通过对以前未见过的值使用静态霍夫曼编码,并将索引替换为已存在于客户端和服务端静态或动态表中的值的索引,可以减少每个请求的大小。
8. 二进制帧的简短介绍
所有HTTP/2改进的核心是新的二进制长度前缀成帧层。与以换行符分隔的纯文本HTTP/1.x协议相比,二进制框架提供了更紧凑的表示形式,可以更高效地处理并更容易正确实现。
一旦建立了HTTP/2连接,客户端和服务器就通过交换帧来进行通信,这些帧用作协议内最小的通信单元。所有帧共享一个共同的9字节头(图12-7),其中包含帧的长度,类型,标志位字段和31位流标识符。

24位长度字段允许一个帧携带2的24次方数据字节。
8位类型字段确定帧的格式和语义。
8位标志字段传递帧类型特定的布尔标志。
1位保留字段始终设置为0。
31位流标识符唯一标识HTTP/2流。
从技术上讲,长度字段允许每帧高达字节(〜16MB)的有效载荷。但是HTTP/2标准将DATA帧的默认最大有效负载大小设置为每帧字节(〜16KB),并允许客户端和服务器协商较高的值。更大并不总是更好:较小的帧大小能够实现高效的多路复用并将头部阻塞降至最低。
9. 分析二进制帧数据流
掌握了不同帧类型的知识后,我们现在可以重新看下我们前面在请求和响应复用中遇到的图(图12-10)并分析HTTP/2交换:

有三个流,ID设置为1,3和5。
所有三个流ID都是奇数;所有这三个都是客户端启动的流(即发起方是客户端)。
在这个交换中没有服务器启动(“推送”)流(即服务端推送)。
服务器正在为流1发送交错数据帧,这些数据帧携带应用程序响应客户端先前的请求。
服务器已经在数据帧之间为流3交错了HEADERS和DATA帧,以便实现流1响应多路复用!
客户端正在传输数据流5的数据帧,这表明HEADERS帧已在先传输。
当然,上述分析基于实际HTTP/2交换的简化表示,但它仍然说明了新协议的许多优点和特点。
参考来源:
https://hpbn.co/http2/
https://www.jianshu.com/p/67c541a421f9
https://imququ.com/post/header-compression-in-http2.html
如下看完上文对HTTP2的理解还是存有疑问,就接着看看下文:《HTTP2协议解析》,转自sunny4handsome的博客空间,感谢原作者。
一、HTTP2 解决什么问题
HTTP2的提出肯定是为了解决HTTP1.1已经存在的问题。所以HTTP1.1存在那些问题呢?
1.1 TCP连接数限制
因为并发的原因一个TCP连接在同一时刻可能发送一个http请求。所以为了更快的响应前端请求,浏览器会建立多个tcp连接,但是第一tcp连接数量是有限制的。现在的浏览器针对同一域名一般最多只能创建6~8个请求;第二创建tcp连接需要三次握手,增加耗时、cpu资源、增加网络拥堵的可能性。所以缺点明显。
1.2 线头阻塞 (Head Of Line Blocking) 问题
每个 TCP 连接同时只能处理一个请求 - 响应,浏览器按 FIFO 原则处理请求,如果上一个响应没返回,后续请求 - 响应都会受阻。为了解决此问题,出现了 管线化 - pipelining 技术,但是管线化存在诸多问题,比如第一个响应慢还是会阻塞后续响应、服务器为了按序返回相应需要缓存多个响应占用更多资源、浏览器中途断连重试服务器可能得重新处理多个请求、还有必须客户端 - 代理 - 服务器都支持管线化。
1.3 Header 内容多
每次请求 Header不会变化太多,没有相应的压缩传输优化方案。特别是想cookie这种比较长的字段。对于HTTP1.1存在的这些问题,是有一定的优化方案的,比如用对个域名,文件合并等。
二、基本概念
数据流: 已建立的连接内的双向字节流,可以承载一条或多条消息。
消息: 与逻辑请求或响应消息对应的完整的一系列帧。
帧: HTTP/2 通信的最小单位,每个帧都包含帧头,至少也会标识出当前帧所属的数据流。
这些概念的关系总结如下:
所有通信都在一个 TCP 连接上完成,此连接可以承载任意数量的双向数据流。
每个数据流都有一个唯一的标识符和可选的优先级信息,用于承载双向消息。
每条消息都是一条逻辑 HTTP 消息(例如请求或响应),包含一个或多个帧。
帧是最小的通信单位,承载着特定类型的数据,例如 HTTP 标头、消息负载等等。来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。

三、HTTP2特性有那些
需要强调的是HTTP/2 是对之前 HTTP 标准的扩展,而非替代。 HTTP 的应用语义不变,提供的功能不变,HTTP 方法、状态代码、URI和标头字段等这些核心概念也不变。已经知道http1.x的报文格式由开始行,首部行,实体主体三部分组成。HTTP2将开始行,首部行封装成帧。实体主体封装成帧。这里的帧是HTTP/2所有性能增强的核心。它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。下图可以很好帮助大家理解http1.x和http2的关系。


HTTP2特性包含一下几个方面
二进制分帧
多路复用
头部压缩
服务端推送(server push)
流量控制
资源优先级和依赖设置
3.1 二进制分帧
帧是数据传输的最小单位,以二进制传输代替原本的明文传输,原本的报文消息被划分为更小的数据帧:

简言之,HTTP/2 将 HTTP 协议通信分解为二进制编码帧的交换,这些帧对应着特定数据流中的消息。所有这些都在一个 TCP 连接内复用。 这是 HTTP/2 协议所有其他功能和性能优化的基础。
3.1.1 HTTP2报文格式
所有帧都是一个固定的 9 字节头部 (payload 之前) 跟一个指定长度的负载 (payload),格式如下。

Length:无符号的自然数,24个比特表示,仅表示帧负载(Frame Payload)所占用字节数,不包括帧头所占用的9个字节。 默认大小区间为为0~16,384(214),一旦超过默认最大值214(16384),发送方将不再允许发送,除非接收到接收方定义的SETTINGS_MAX_FRAME_SIZE(一般此值区间为2^14 ~ 2^24)值的通知
Type:定义 frame 的类型,用 8 bits 表示。帧类型决定了帧主体的格式和语义,如果 type 为 unknown 应该忽略或抛弃
Flags:是为帧类型相关而预留的布尔标识。标识对于相同同的帧类型赋予了不同的语义
R:是一个保留的比特位。这个比特的语义没有定义,发送时它必须被设置为 (0x0), 接收时需要忽略
Stream Identifier :用作流控制,用 31 位无符号整数表示。客户端建立的 sid 必须为奇数,服务端建立的 sid 必须为偶数,值 (0x0) 保留给与整个连接相关联的帧 (连接控制消息),而不是单个流
Frame Payload:是主体内容,由帧类型决定
HTTP2共分为十种类型的帧:

HEADERS: 报头帧 (type=0x1),用来打开一个流或者携带一个首部块片段
DATA: 数据帧 (type=0x0),装填主体信息,可以用一个或多个 DATA 帧来返回一个请求的响应主体
PRIORITY: 优先级帧 (type=0x2),指定发送者建议的流优先级,可以在任何流状态下发送 PRIORITY 帧,包括空闲 (idle) 和关闭 (closed) 的流
RST_STREAM: 流终止帧 (type=0x3),用来请求取消一个流,或者表示发生了一个错误,payload 带有一个 32 位无符号整数的错误码 (Error Codes),不能在处于空闲 (idle) 状态的流上发送 RST_STREAM 帧
SETTINGS: 设置帧 (type=0x4),设置此 连接 的参数,作用于整个连接
PUSH_PROMISE: 推送帧 (type=0x5),服务端推送,客户端可以返回一个 RST_STREAM 帧来选择拒绝推送的流
PING: PING 帧 (type=0x6),判断一个空闲的连接是否仍然可用,也可以测量最小往返时间 (RTT)
GOAWAY: GOWAY 帧 (type=0x7),用于发起关闭连接的请求,或者警示严重错误。GOAWAY 会停止接收新流,并且关闭连接前会处理完先前建立的流
WINDOW_UPDATE: 窗口更新帧 (type=0x8),用于执行流量控制功能,可以作用在单独某个流上 (指定具体 Stream Identifier) 也可以作用整个连接 (Stream Identifier 为 0x0),只有 DATA 帧受流量控制影响。初始化流量窗口后,发送多少负载,流量窗口就减少多少,如果流量窗口不足就无法发送,WINDOW_UPDATE 帧可以增加流量窗口大小
CONTINUATION: 延续帧 (type=0x9),用于继续传送首部块片段序列,见 首部的压缩与解压缩
HTTP2 帧和flags的可能组合示意图:

不同类型的帧可能对应的flags
表中x符号表示该类型的帧的flags可以取的值,下面看一些几种常见的帧完整结构
3.1.1.1 DATA 帧格式
DATA 帧的type为0x0。

DATA Frame Payload
Pad Length:? 表示此字段的出现时有条件的,需要设置相应标识 (set flag),指定 Padding 长度,存在则代表 PADDING flag 被设置
Data:传递的数据,其长度上限等于帧的 payload 长度减去其他出现的字段长度
Padding:填充字节,没有具体语义,发送时必须设为 0,作用是混淆报文长度,与 TLS 中 CBC 块加密类似
DATA 帧有如下标识 (flags):
END_STREAM: bit 0 设为 1 代表当前流的最后一帧
PADDED: bit 3 设为 1 代表存在 Padding
3.1.1.2 HEADERS 帧格式

HEADERS Frame Payload
Pad Length: 指定 Padding 长度,存在则代表 PADDING flag 被设置
E: 一个比特位声明流的依赖性是否是排他的,存在则代表 PRIORITY flag 被设置
Stream Dependency: 指定一个 stream identifier,代表当前流所依赖的流的 id,存在则代表 PRIORITY flag 被设置
Weight: 一个无符号 8 为整数,代表当前流的优先级权重值 (1~256),存在则代表 PRIORITY flag 被设置
Header Block Fragment: header 块片段
Padding: 填充字节,没有具体语义,作用与 DATA 的 Padding 一样,存在则代表 PADDING flag 被设置
HEADERS 帧有以下标识 (flags):
END_STREAM: bit 0 设为 1 代表当前 header 块是发送的最后一块,但是带有 END_STREAM 标识的 HEADERS 帧后面还可以跟 CONTINUATION 帧 (这里可以把 CONTINUATION 看作 HEADERS 的一部分)
END_HEADERS: bit 2 设为 1 代表 header 块结束
PADDED: bit 3 设为 1 代表 Pad 被设置,存在 Pad Length 和 Padding
PRIORITY: bit 5 设为 1 表示存在 Exclusive Flag (E), Stream Dependency, 和 Weight
3.1.1.3 SETTINGS 帧格式
一个 SETTINGS 帧的 payload 由零个或多个参数组成,每个参数的形式如下:

SETTINGS Format
在建立连接开始时双方都要发送 SETTINGS 帧以表明自己期许对方应做的配置,对方接收后同意配置参数便返回带有 ACK 标识的空 SETTINGS 帧表示确认,而且连接后任意时刻任意一方也都可能再发送 SETTINGS 帧调整,SETTINGS 帧中的参数会被最新接收到的参数覆盖
SETTINGS 帧作用于整个连接,而不是某个流,而且 SETTINGS 帧的 stream identifier 必须是 0x0,否则接收方会认为错误 (PROTOCOL_ERROR)。
SETTINGS 帧包含以下参数:
SETTINGS_HEADER_TABLE_SIZE (0x1): 用于解析 Header block 的 Header 压缩表的大小,初始值是 4096 字节
SETTINGS_ENABLE_PUSH (0x2): 可以关闭 Server Push,该值初始为 1,表示允许服务端推送功能
SETTINGS_MAX_CONCURRENT_STREAMS (0x3): 代表发送端允许接收端创建的最大流数目
SETTINGS_INITIAL_WINDOW_SIZE (0x4): 指明发送端所有流的流量控制窗口的初始大小,会影响所有流,该初始值是 2^16 - 1(65535) 字节,最大值是 2^31 - 1,如果超出最大值则会返回 FLOW_CONTROL_ERROR
SETTINGS_MAX_FRAME_SIZE (0x5): 指明发送端允许接收的最大帧负载的字节数,初始值是 2^14(16384) 字节,如果该值不在初始值 (2^14) 和最大值 (2^24 - 1) 之间,返回 PROTOCOL_ERROR
SETTINGS_MAX_HEADER_LIST_SIZE (0x6): 通知对端,发送端准备接收的首部列表大小的最大字节数。该值是基于未压缩的首部域大小,包括名称和值的字节长度,外加每个首部域的 32 字节的开销
SETTINGS 帧有以下标识 (flags):
ACK: bit 0 设为 1 代表已接收到对方的 SETTINGS 请求并同意设置,设置此标志的 SETTINGS 帧 payload 必须为空
3.1.1.4 PRIORITY 帧格式

PRIORITY 帧可以在流的任何状态使用,Header帧中优先级是在打开的时候,注意区别。字段含义和header帧中的一样。PRIORITY只可作用于特定的流,不可作用于整个连接
3.1.1.5 RST_STREAM 帧格式

RST_STREAM帧用于立刻终止一个流
3.1.1.6 PUSH_PROMISE 帧格式

Pad Length: 指定 Padding 长度,存在则代表 PADDING flag 被设置
R: 保留的1bit位
Promised Stream ID: 31 位的无符号整数,代表PUSH_PROMISE 帧保留的流,对于发送者来说该流标识符必须是可用于下一个流的有效值(该标识是偶数)
Header Block Fragment: 包含请求首部域的首部块片段
Padding: 填充字节,没有具体语义,作用与 DATA 的 Padding 一样,存在则代表 PADDING flag 被设置
PUSH_PROMISE 帧有以下标识 (flags):
END_HEADERS: bit 2 置 1 代表 header 块结束
PADDED: bit 3 置 1 代表 Pad 被设置,存在 Pad Length 和 Padding
3.1.1.7 PING 帧格式

用于判断空闲连接是否可用。
PING 帧有以下标识 (flags):
ACK (0x1):设置为0表示对ping帧的回复
3.1.1.8 GOAWAY 帧格式

3.1.1.9 WINDOW_UPDATE 帧格式
WINDOW_UPDATE用于流量控制,可作用于整个连接或者流

Window Size Increment 表示除了现有的流量控制窗口之外,发送端还可以传送的字节数。取值范围是 1 到 2^31 - 1 字节
3.1.1.10 CONTINUATION 帧格式

3.2 多路复用
简而言之:多个http请求可以共用同一个TCP连接。

3.2.1 为什么http1.1不能实现多路复用
http1.1 是基于文本分割协议的。我们不知道一个请求什么时候结束,只能一直读取,直到出现空行(http请求结果标志)。所以就不能使用多路复用。要不然就不知道哪个消息是属于哪个请求了。但是HTTP2引入二进制分帧,用 stream id标识帧和请求的对应关系。
3.3 头部压缩
HTTP2使用的HPACK作为头部压缩算法。

可以清楚地看到 HTTP2 头部使用的也是键值对形式的值,而且 HTTP1 当中的请求行以及状态行也被分割成键值对,还有所有键都是小写,不同于 HTTP1。除此之外,还有一个包含静态索引表和动态索引表的索引空间,实际传输时会把头部键值表压缩,使用的算法即 HPACK,其原理就是匹配当前连接存在的索引空间,若某个键值已存在,则用相应的索引代替首部条目,比如 “:method: GET” 可以匹配到静态索引中的 index 2,传输时只需要传输一个包含 2 的字节即可;若索引空间中不存在,则用字符编码传输,字符编码可以选择哈夫曼编码,然后分情况判断是否需要存入动态索引表中。关于详细的压缩过程见参考文献10。
3.4 server push
服务端主动推送,如下图,page.html包含script.js和style.css资源文件。客户端只需要请求page.html,服务端发现page.html中包含资源文件会主动推送给客户端。减少客户端请求的次数。

所有服务器推送数据流都由 PUSH_PROMISE 帧发起,表明了服务器向客户端推送所述资源的意图,并且需要先于请求推送资源的响应数据传输。 这种传输顺序非常重要: 客户端需要了解服务器打算推送哪些资源,以免为这些资源创建重复请求。 满足此要求的最简单策略是先于父响应(即,DATA 帧)发送所有 PUSH_PROMISE 帧,其中包含所承诺资源的 HTTP 标头。
3.5 流量控制
多路复用的流会竞争 TCP 资源,进而导致流被阻塞。流控制机制确保同一连接上的流不会相互干扰。流量控制作用于单个流或整个连接。HTTP/2 通过使用 WINDOW_UPDATE 帧来提供流量控制。例如,客户端可能请求了一个具有较高优先级的大型视频流,但是用户已经暂停视频,客户端现在希望暂停或限制从服务器的传输,以免提取和缓冲不必要的数据。
流量控制是特定于连接的。两种级别的流量控制都位于单跳的端点之间,而不是整个端到端的路径。比如 server 前面有一个 front-end proxy 如 Nginx,这时就会有两个 connection,browser-Nginx, Nginx—server,flow control 分别作用于两个 connection。
流量控制是基于 WINDOW_UPDATE 帧的。接收方公布自己打算在每个流以及整个连接上分别接收多少字节。这是一个以信用为基础的方案。
流量控制是有方向的,由接收者全面控制。接收方可以为每个流和整个连接设置任意的窗口大小。发送方必须尊重接收方设置的流量控制限制。客户方、服务端和中间代理作为接收方时都独立地公布各自的流量控制窗口,作为发送方时都遵守对端的流量控制设置。
无论是新流还是整个连接,流量控制窗口的初始值是 65535 字节。
帧的类型决定了流量控制是否适用于帧。目前,只有 DATA 帧会受流量控制影响,所有其它类型的帧并不消耗流量控制窗口的空间。这保证了重要的控制帧不会被流量控制阻塞。
流量控制不能被禁用。
HTTP/2 只定义了 WINDOW_UPDATE 帧的格式和语义,并没有规定接收方如何决定何时发送帧、发送什么样的值,也没有规定发送方如何选择发送包。具体实现可以选择任何满足需求的算法。
3.6 资源优先级和依赖设置
客户端可以通过 HEADERS 帧的 PRIORITY 信息指定一个新建立流的优先级,其他期间也可以发送 PRIORITY 帧调整流优先级
每个流都可以显示地依赖另一个流,包含依赖关系表示优先将资源分配给指定的流(上层节点)而不是依赖流
HTTP/2是2015年新发布的HTTP协议,旨在提高了资源访问效率。HTTP/2也被称为HTTP 2.0,相对于HTTP 1.1新增了多路复用、压缩HTTP头、划分请求优先级、服务端推送等特性,解决了在HTTP 1.1中一直存在的问题,优化了请求性能,同时兼容了HTTP 1.1的语义。是现行HTTP协议(HTTP/1.1)的兼容升级版本,HTTP方法、状态码、语义与HTTP/1.1一样。HTTP/2 相比于 HTTP/1.1,可以说是大幅度提高了网页的性能,只需要升级到该协议就可以减少很多之前需要做的性能优化工作。它基于SPDY,专注于性能,最大的一个目标是在用户和网站间只用一个连接(connection)。
新特性
二进制传输、Header压缩、多路复用、服务器推送。
HTTP/1.1的缺陷主要包含连接无法复用、队头阻塞、协议开销大和安全因素,而HTTP/2 通过多路复用、二进制流、Header 压缩等技术,极大地提高了性能,弥补了这些缺陷。HTTP/2通过支持请求与响应的多路复用来减少延迟,通过压缩HTTP首部字段将协议开销降至最低,同时增加对请求优先级和服务器端推送的支持。目前百度云加速已完全支持HTTP/2,免费版和收费版默认开启此功能,无需用户进行任何配置操作,HTTP/2将取代HTTP/1.1被广泛采用。
主要从以下两个方面来优化:
1、头部压缩
HTTP1.1主要是对Body进行压缩,而头部却没有压缩。HTTP2通过HPACK算法对头部进行压缩,减少了传输时间
2、队头阻塞
HTTP1.1使用的是TCP协议,并且为了节省资源,采用了长连接,长连接引入了队头阻塞的问题。HTTP2引入了流和帧,解决了HTTP层面上的队头阻塞
HTTP2报文结构
1、二进制替换文本
HTTP1.1采用的是文本描述,也就是通过ASCII文本进行传输,采用文本描述的好处是调试程序方便,能够直接看出数据包中的数据情况。而HTTP2使用的二进制来进行传输,用01串来描述数据,调试和查看数据的具体含义就不那么方便了。但是这种二进制传输使的计算机解析方便,体积小,性能高。
举个例子,比如之前需要传递对应的文本值来表示对应的选择,而通过二进制编码就能知道编码对应的选择,一个二进制编码对应着一个文本值。二进制编码肯定比文本占用的空间小。
2、HTTP2将HTTP1中数据包分解为了帧
将HTTP1.1的数据包分为HEADERS帧和DATA帧,属于是化整为零,为的就是多路复用,具体原理下文再说。


HTTP2连接过程
连接前言
当TCP三次握手成功后,客户端需要向服务器发送一个连接前言。这是一个HTTP1.1报文,是一个ASCII文本,里面记录着一个magic字符串,当服务器接收到这个请求后,服务器就知道客户端想要使用HTTP2协议。
头部压缩
确定了HTTP2连接后,就开始准备发送HTTP2请求了。
首先就是发送HEADERS帧,HTTP1.1中对Body进行GZIP等压缩方式,但是对于头部却没有采取任何办法。HTTP2采用了 HPACK 算法来压缩,HPACK 中客户端和服务器都要维护一个索引表。

如上图所示,索引表中维护了头部中Key-Value的对应关系和索引下标。到时候,HTTP2请求中头部不用再发送Key-Value了,直接发送对应的索引下标就可以了。
上述的索引表是一个静态的表,记录着常用的头字段。HTTP的起始行中记录了Method、StatusCode等信息,也被记录到了索引表中。
有的HTTP2头部有着特殊的头部字段,所以HPACK又在静态表的尾部开始维护一个动态记录。当第一次发送索引表中不存在的Key-Value的时候,会使用实际数据发送,客户端和服务器对应的索引表会对其缓存,等到下一次发送的时候,就可以直接发送索引下标了。
所以说,HTTP2通过HPACK算法,将每次发送Key-Value替换成了发送一个下标,极大的减少了HTTP2头部的数据量,传输效率极大的提高了。
二进制帧
HTTP2将之前的head+body的一个数据包分为了HEADER帧和若干个DATA帧格式,如下图所示:

1、帧长度
帧的总长度,应用程序按照长度来读取
2、帧类型
HTTP2中有 数据帧、控制帧等类别,控制帧有着更高的传输优先权
3、标志位
8个bit,可以代表8个状态,比如END_HEADERS代表是否头数据传输结束、END_STREAM表示单方面传输数据结束
4、流标识符
帧属于哪个流,每个HTTP包传输都是一个虚拟流,通过流标识符才能将前后发送的帧重组为一个完整的数据包
5、实际传输的数据
HTTP2交互过程
HTTP2利用帧+流的方式解决了HTTP1中的队头阻塞问题。
HTTP1中每个HTTP包复用一个TCP连接,每次只能等到前一个HTTP响应后才能发送后一个HTTP请求,如果前一个HTTP响应阻塞的话,后面的HTTP请求都会跟着阻塞,这就是队头阻塞问题。
HTTP2也是利用的长连接,多个HTTP请求复用一个TCP连接。但是HTTP2将每个HTTP请求都看作一个流,将HTTP数据包分解为多个帧,包括HEADER帧和DATA帧。每个帧都对应着一个流标识号。
多个HTTP请求之间的发送是乱序的,但是每个HTTP请求中的帧的发送是有序的。
虽然多个HTTP请求之间的发送请求是乱序的,但是可以基于流标识符来进行重组,一个流标识符对应着一个HTTP请求。

流标识符不能复用,是自动递增的,客户端使用的是奇数,服务器使用的偶数。

这就解决了队头阻塞问题。下面是从外文网站转过来,很好地补充了上文。
HTTP/2更简单、高效、强大,它在传输层解决了以前我们HTTP1.x中一直存在的问题,使用它可以优化我们的应用。其首要目标是通过完全的请求,响应多路复用,头部的压缩头部域来减小头部的体积,添加了请求优先级,服务端推送.为了支持这些特性,他需要大量的协议增加头部字段来支持,例如新的流量控制,差错处理,升级机制.而这些是每个web开发者都应该在他们的应用中用到的.
HTTP/2并没有在应用中改变HTTP的语义,而是通过在客户端和服务端传输的数据格式(frame)和传输.它通过在新的二进制帧层控制整个过程以及隐藏复杂性,而这不需要改变原来有的东西就可以实现.
1. 设计和技术目标
HTTP是因特网广泛普及和采纳的应用层协议.它的易于实现性同样有了对应用性能方面的影响.HTTP/1.x 需要开启多个连接来实现并发和减少潜在影响.HTTP/1.x 的头部没有压缩会造成不必要的网络拥塞.HTTP/1.x没有应用资源优先级,导致重要Tcp连接的糟糕使用.
它的好处如下:
HTTP/2 enables a more efficient use of network resources and a reduced perception of latency by introducing header field compression and allowing multiple concurrent exchanges on the same connection… Specifically, it allows interleaving of request and response messages on the same connection and uses an efficient coding for HTTP header fields. It also allows prioritization of requests, letting more important requests complete more quickly, further improving performance.
The resulting protocol is more friendly to the network, because fewer TCP connections can be used in comparison to HTTP/1.x. This means less competition with other flows, and longer-lived connections, which in turn leads to better utilization of available network capacity. Finally, HTTP/2 also enables more efficient processing of messages through use of binary message framing.
HTTP2.0并没有改变之前HTTP的语义,也就是说高层的Api并没有改变,它是在底层通过二进制frame来改变性能的.
2. 二进制帧层
性能提升的核心在于二进制帧层.它指HTTP消息在客户端和服务端如何封装和传输.

这一层指一个设计选择,它在socket接口之间采用一种更好的编码机制,而高层的Api提供给我们的应用。与HTTP1.x的采用的换行符分隔文本不同,HTTP/2 消息被分成很小的消息和frame,然后每个消息和frame用二进制编码。客户端和服务端都采用二进制编码和解码。HTTP/1.x 的客户端不能与只有HTTP/2的服务端通信。幸运的是,我们的应用还没意识到这些改变。客户端和服务端能够很好的处理这些帧。
ASCII 协议能够很容易的看出来和开始使用。然而它们是没有效率的,且很难正确设计:可选的空白,改变终止序列和其他的毛病使得协议很难区别出payload。虽然二进制协议用起来需要做很多工作,但是它们能表现出更好的性能。
3. 流,消息,帧
接下来介绍二进制帧机制来明白数据如何在客户端和服务端交换的。
流:已经建立的连接之间双向流动的字节,它能携带一个至多个消息。
消息:一个完整的帧序列,它映射到逻辑的请求和响应消息。
帧:在HTTP/2通信的最小单元。每个桢包括一个帧头,里面有个很小标志,来区别是属于哪个流。
所有的通信都建立在一个TCP连接上,可以传递大量的双向流通的流。
每个流都有独一无二的标志和优先级。
每个消息都是逻辑上的请求和相应消息。由一个或者多个帧组成。
来自不同流的帧可以通过帧头的标志来关联和组装起来。

这是 HTTP/2 协议提供高性能的基础。
4. 请求和响应的多路复用
在HTTP/1.x中,用户想要多个并行的请求来提高性能,但是这样必须得使用多个TCP连接.这样的操作是属于HTTP/1.x 发送模型的直接序列.它能保证在每次连接中在一个时间点只有一个响应被发送出去.更糟糕的是,它使得队头阻塞和重要TCP连接的低效使用.
在HTTP/2中,新的二进制帧层,解除了这个限制.使得所有的请求和响应多路复用.通过允许客户端和服务端把HTTP消息分解成独立的帧,交错传输,然后在另一端组装.

图12-3显示了在一次连接中的多个流.客户端传输数据帧到服务端(Stream5).服务端传输交错的帧序列(Stream1,Stream3)到客户端.此时,同时存在并行的3个流.
能够把HTTP消息分解成交错的帧,并在另一端组装它们是HTTP/2中一个非常重要的提高.事实上,它引起了一种波浪效应使得web技术的全栈在性能上有很大的提升.它有以下作用:
交错的多个并行的请求或者,而不需要阻塞.
使用一个连接传递所有的并行的请求和响应.
移除了HTTP/1.x中没有的必要的解决方法.例如级联文件,域分片.
淘汰没必要的潜在因素来降低页面载入的时间.提升可用网络容积的使用率.
新的二进制帧层解决了HTTP/1.X中头部阻塞的问题.在并行处理和传输的请求和响应不再需要多个连接.这使得我们的应用更简单,快捷和便宜.
5. 流的优先级
为了能方便流的传输顺序,HTTP/2.0提出,使每个流都有一个权重和依赖.
每个流的权重值在1~256之间
每个流可以详细给出对其他流的依赖
流权重和依赖的结合使客户端可以构造和通信一个优先级二叉树来表达它更想得到哪种响应.然后服务端可以按权重分配硬件资源(CPU,内存).

在HTTP/2 ,一个流的依赖可以显式用其他流的标志来表达,如果省略了标志,则说明它的依赖是根流.一般来说,父流应该在它的依赖流之前分配资源,例如D应该是C之前被发送.依赖于同一父节点的应该按照他们的权重分配资源.例如A结点的权重为12,它的兄弟结点B的结点的权重为4.然后按比例分资源,A占12/16,B占4/16.如上面所述,流的依赖和权重提供了一种很好的表达式语言来表达资源的优先级.但是我们应该明白,,流的依赖和权重只是提供了一种传输偏好,而不是说一定是这样的比例.
5.1 每个源一个连接
HTTP/2.0的连接是持久的,每个源仅仅需要一个连接.大部分HTTP的传输是短的,并且突然的.然而TCP连接却适合长期存活的,批量的数据传输.通过利用相同的HTTP/2 连接,既能够充分利用TCP连接,也能减小整体协议的头部.更进一步来说,更少的连接内存的占用以及全连接路径的处理过程.向HTTP/2的转移不仅减少了网络潜在因素,更减少了操作代价.
Tips:减少连接,同时也提高了HTTPS的性能,因为仅需要更少的TLS层的握手.
5.2 流量控制
流量控制是一种机制,用来阻止发送者发送大量的接收者不需要,或者没能力处理的数据.接收者可能会在重负下很繁忙,或者只愿意分配固定的资源给特定的流.例如,客户端可能以高的优先级请求大量的视频数据,然后用户暂停了视频,那么客户端现在想要停止或者减少服务端的传输来避免取和缓存没必要的数据.或者一个代理服务器连接有很快的下流,很慢的上流,同样的也要控制以多大的流速传输数据,从而匹配上流的速度,从而控制资源的使用.
这些需求可能让你想起了TCP流量控制,由于HTTP/2的那些流是在一个TCP的连接上.那么TCP连接不够细粒度,也没能提供应用级的API来控制单个流的传输.为了应对这种情况,HTTP/2提供了一系列的简单修筑块,来允许客户端和服务端实现他们自己的流级别的,连接级别的流量控制.
流量控制是有方向的.对于每个流和连接,每个接收者可以设置它想要窗口大小.
流量控制是基于信用的。每个接收者通告其初始连接和流量控制窗口(以字节为单位),只要发送者发送数据帧并通过接受者发送的WINDOW_UPDATE帧递增,该窗口就会减少。
流量控制不能禁用.当建立HTTP / 2连接时,客户端和服务器交换SETTINGS帧,这些帧设置双向流量控制窗口的大小。流量控制窗口的默认值设置为65,535字节,但接收方可以设置更大的最大窗口大小(2的31次方-1字节),并通过在接收到任何数据时发送WINDOW_UPDATE帧来维护它。
流量控制是逐跳的,而不是端到端的.也就是说,一个中介可以使用它控制资源的使用,从而根据自己的标准和启发式实现资源分配机制.
HTTP/2没有规定用于实现流量控制的任何特定算法。相反,它提供了简单的构建模块并将实现推迟到客户端和服务器,这可以用它来实现自定义策略来调节资源使用和分配,以及实现新的传输功能,这可能有助于提高Web应用程序真实性和感知性。例如,应用程序层流控量制允许浏览器仅提取特定资源的一部分,通过将流量控制窗口降至零来暂停提取,然后稍后恢复。又例如,获取预览或第一次浏览图像,显示图像并允许进行其他高优先级操作取来操作,并在关键资源完成加载后又开始取。
6.服务端推送
HTTP/2的另一个强大的新功能是服务器为单个客户端请求发送多个响应的能力。也就是说,除了对原始请求的响应之外,服务器还可以向客户端推送额外的资源(图12-5),而不需要客户端明确请求每一个资源!

HTTP/2脱离了严格的请求 - 响应语义,并支持一对多和服务器启动的推送工作流程,在浏览器内部和外部打开全新的交互可能性。这是一个启动功能,对于我们如何考虑协议以及在何处以及如何使用协议,都会产生重要的长期影响。
为什么我们需要在浏览器中使用这种机制?一个典型的Web应用程序由几十个资源组成,所有这些资源都是客户端通过检查服务器提供的文档发现的。因此,为什么不消除额外的延迟并让服务器提前推送相关资源?服务器已经知道客户端需要哪些资源;这是服务器推动。事实上,如果您曾经通过数据URI将CSS,JavaScript或任何其他资产内联到一起(请参阅资源内联),那么您已经有了服务器推送的实践经验!通过手动将资源内联到文档中,实际上,我们将该资源推送到客户端,而无需等待客户端请求。通过HTTP/2,我们可以获得相同的结果,但是具有额外的性能优势:
推送的资源可以由客户端缓存
推送的资源可以在不同的页面上重复使用
推送的资源可以与其他资源一起复用
推送的资源可以由服务器优先
推送的资源可以被客户拒绝
每个推送的资源都是一个流,与内联资源不同,它允许客户端对其进行单独复用,优先化和处理。由浏览器执行的唯一安全限制是推送资源必须遵守同源策略:服务器必须对提供的内容具有权限。
7. 头部压缩
每个HTTP传输都包含一组描述传输资源及其属性的标题。在HTTP/1.x中,此元数据始终以纯文本形式发送,并且每次传输的开销都会在任何位置增加500-800字节,如果使用HTTP Cookie,则会增加数千字节。为了减少这种开销并提高性能,HTTP/2使用两种简单但强大的技术使用HPACK压缩格式(要了解这个算法可以参考这篇文章)来压缩请求和响应头元数据:
它允许通过静态霍夫曼编码对传输的头部字段进行编码,从而减少它们各自的传输大小。
它要求客户端和服务器都维护和更新先前看到的标题字段的索引列表(即,建立共享压缩上下文),然后将其用作参考以高效编码先前传输的值。
霍夫曼编码允许单个值在传输时被压缩,并且先前传输值的索引列表允许我们通过传输索引值来编码重复值(图12-6),索引值可用于有效地查找和重建完整头部键和值。

作为进一步优化,HPACK压缩上下文由静态和动态表组成:静态表在规范中定义,并提供所有连接可能使用的常见HTTP头字段的列表(例如,有效头名称);动态表最初是空的,并基于特定连接内的交换值进行更新。因此,通过对以前未见过的值使用静态霍夫曼编码,并将索引替换为已存在于客户端和服务端静态或动态表中的值的索引,可以减少每个请求的大小。
8. 二进制帧的简短介绍
所有HTTP/2改进的核心是新的二进制长度前缀成帧层。与以换行符分隔的纯文本HTTP/1.x协议相比,二进制框架提供了更紧凑的表示形式,可以更高效地处理并更容易正确实现。
一旦建立了HTTP/2连接,客户端和服务器就通过交换帧来进行通信,这些帧用作协议内最小的通信单元。所有帧共享一个共同的9字节头(图12-7),其中包含帧的长度,类型,标志位字段和31位流标识符。

24位长度字段允许一个帧携带2的24次方数据字节。
8位类型字段确定帧的格式和语义。
8位标志字段传递帧类型特定的布尔标志。
1位保留字段始终设置为0。
31位流标识符唯一标识HTTP/2流。
从技术上讲,长度字段允许每帧高达字节(〜16MB)的有效载荷。但是HTTP/2标准将DATA帧的默认最大有效负载大小设置为每帧字节(〜16KB),并允许客户端和服务器协商较高的值。更大并不总是更好:较小的帧大小能够实现高效的多路复用并将头部阻塞降至最低。
9. 分析二进制帧数据流
掌握了不同帧类型的知识后,我们现在可以重新看下我们前面在请求和响应复用中遇到的图(图12-10)并分析HTTP/2交换:

有三个流,ID设置为1,3和5。
所有三个流ID都是奇数;所有这三个都是客户端启动的流(即发起方是客户端)。
在这个交换中没有服务器启动(“推送”)流(即服务端推送)。
服务器正在为流1发送交错数据帧,这些数据帧携带应用程序响应客户端先前的请求。
服务器已经在数据帧之间为流3交错了HEADERS和DATA帧,以便实现流1响应多路复用!
客户端正在传输数据流5的数据帧,这表明HEADERS帧已在先传输。
当然,上述分析基于实际HTTP/2交换的简化表示,但它仍然说明了新协议的许多优点和特点。
参考来源:
https://hpbn.co/http2/
https://www.jianshu.com/p/67c541a421f9
https://imququ.com/post/header-compression-in-http2.html
如下看完上文对HTTP2的理解还是存有疑问,就接着看看下文:《HTTP2协议解析》,转自sunny4handsome的博客空间,感谢原作者。
一、HTTP2 解决什么问题
HTTP2的提出肯定是为了解决HTTP1.1已经存在的问题。所以HTTP1.1存在那些问题呢?
1.1 TCP连接数限制
因为并发的原因一个TCP连接在同一时刻可能发送一个http请求。所以为了更快的响应前端请求,浏览器会建立多个tcp连接,但是第一tcp连接数量是有限制的。现在的浏览器针对同一域名一般最多只能创建6~8个请求;第二创建tcp连接需要三次握手,增加耗时、cpu资源、增加网络拥堵的可能性。所以缺点明显。
1.2 线头阻塞 (Head Of Line Blocking) 问题
每个 TCP 连接同时只能处理一个请求 - 响应,浏览器按 FIFO 原则处理请求,如果上一个响应没返回,后续请求 - 响应都会受阻。为了解决此问题,出现了 管线化 - pipelining 技术,但是管线化存在诸多问题,比如第一个响应慢还是会阻塞后续响应、服务器为了按序返回相应需要缓存多个响应占用更多资源、浏览器中途断连重试服务器可能得重新处理多个请求、还有必须客户端 - 代理 - 服务器都支持管线化。
1.3 Header 内容多
每次请求 Header不会变化太多,没有相应的压缩传输优化方案。特别是想cookie这种比较长的字段。对于HTTP1.1存在的这些问题,是有一定的优化方案的,比如用对个域名,文件合并等。
二、基本概念
数据流: 已建立的连接内的双向字节流,可以承载一条或多条消息。
消息: 与逻辑请求或响应消息对应的完整的一系列帧。
帧: HTTP/2 通信的最小单位,每个帧都包含帧头,至少也会标识出当前帧所属的数据流。
这些概念的关系总结如下:
所有通信都在一个 TCP 连接上完成,此连接可以承载任意数量的双向数据流。
每个数据流都有一个唯一的标识符和可选的优先级信息,用于承载双向消息。
每条消息都是一条逻辑 HTTP 消息(例如请求或响应),包含一个或多个帧。
帧是最小的通信单位,承载着特定类型的数据,例如 HTTP 标头、消息负载等等。来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。

三、HTTP2特性有那些
需要强调的是HTTP/2 是对之前 HTTP 标准的扩展,而非替代。 HTTP 的应用语义不变,提供的功能不变,HTTP 方法、状态代码、URI和标头字段等这些核心概念也不变。已经知道http1.x的报文格式由开始行,首部行,实体主体三部分组成。HTTP2将开始行,首部行封装成帧。实体主体封装成帧。这里的帧是HTTP/2所有性能增强的核心。它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。下图可以很好帮助大家理解http1.x和http2的关系。


HTTP2特性包含一下几个方面
二进制分帧
多路复用
头部压缩
服务端推送(server push)
流量控制
资源优先级和依赖设置
3.1 二进制分帧
帧是数据传输的最小单位,以二进制传输代替原本的明文传输,原本的报文消息被划分为更小的数据帧:

简言之,HTTP/2 将 HTTP 协议通信分解为二进制编码帧的交换,这些帧对应着特定数据流中的消息。所有这些都在一个 TCP 连接内复用。 这是 HTTP/2 协议所有其他功能和性能优化的基础。
3.1.1 HTTP2报文格式
所有帧都是一个固定的 9 字节头部 (payload 之前) 跟一个指定长度的负载 (payload),格式如下。

Length:无符号的自然数,24个比特表示,仅表示帧负载(Frame Payload)所占用字节数,不包括帧头所占用的9个字节。 默认大小区间为为0~16,384(214),一旦超过默认最大值214(16384),发送方将不再允许发送,除非接收到接收方定义的SETTINGS_MAX_FRAME_SIZE(一般此值区间为2^14 ~ 2^24)值的通知
Type:定义 frame 的类型,用 8 bits 表示。帧类型决定了帧主体的格式和语义,如果 type 为 unknown 应该忽略或抛弃
Flags:是为帧类型相关而预留的布尔标识。标识对于相同同的帧类型赋予了不同的语义
R:是一个保留的比特位。这个比特的语义没有定义,发送时它必须被设置为 (0x0), 接收时需要忽略
Stream Identifier :用作流控制,用 31 位无符号整数表示。客户端建立的 sid 必须为奇数,服务端建立的 sid 必须为偶数,值 (0x0) 保留给与整个连接相关联的帧 (连接控制消息),而不是单个流
Frame Payload:是主体内容,由帧类型决定
HTTP2共分为十种类型的帧:

HEADERS: 报头帧 (type=0x1),用来打开一个流或者携带一个首部块片段
DATA: 数据帧 (type=0x0),装填主体信息,可以用一个或多个 DATA 帧来返回一个请求的响应主体
PRIORITY: 优先级帧 (type=0x2),指定发送者建议的流优先级,可以在任何流状态下发送 PRIORITY 帧,包括空闲 (idle) 和关闭 (closed) 的流
RST_STREAM: 流终止帧 (type=0x3),用来请求取消一个流,或者表示发生了一个错误,payload 带有一个 32 位无符号整数的错误码 (Error Codes),不能在处于空闲 (idle) 状态的流上发送 RST_STREAM 帧
SETTINGS: 设置帧 (type=0x4),设置此 连接 的参数,作用于整个连接
PUSH_PROMISE: 推送帧 (type=0x5),服务端推送,客户端可以返回一个 RST_STREAM 帧来选择拒绝推送的流
PING: PING 帧 (type=0x6),判断一个空闲的连接是否仍然可用,也可以测量最小往返时间 (RTT)
GOAWAY: GOWAY 帧 (type=0x7),用于发起关闭连接的请求,或者警示严重错误。GOAWAY 会停止接收新流,并且关闭连接前会处理完先前建立的流
WINDOW_UPDATE: 窗口更新帧 (type=0x8),用于执行流量控制功能,可以作用在单独某个流上 (指定具体 Stream Identifier) 也可以作用整个连接 (Stream Identifier 为 0x0),只有 DATA 帧受流量控制影响。初始化流量窗口后,发送多少负载,流量窗口就减少多少,如果流量窗口不足就无法发送,WINDOW_UPDATE 帧可以增加流量窗口大小
CONTINUATION: 延续帧 (type=0x9),用于继续传送首部块片段序列,见 首部的压缩与解压缩
HTTP2 帧和flags的可能组合示意图:

不同类型的帧可能对应的flags
表中x符号表示该类型的帧的flags可以取的值,下面看一些几种常见的帧完整结构
3.1.1.1 DATA 帧格式
DATA 帧的type为0x0。

DATA Frame Payload
Pad Length:? 表示此字段的出现时有条件的,需要设置相应标识 (set flag),指定 Padding 长度,存在则代表 PADDING flag 被设置
Data:传递的数据,其长度上限等于帧的 payload 长度减去其他出现的字段长度
Padding:填充字节,没有具体语义,发送时必须设为 0,作用是混淆报文长度,与 TLS 中 CBC 块加密类似
DATA 帧有如下标识 (flags):
END_STREAM: bit 0 设为 1 代表当前流的最后一帧
PADDED: bit 3 设为 1 代表存在 Padding
3.1.1.2 HEADERS 帧格式

HEADERS Frame Payload
Pad Length: 指定 Padding 长度,存在则代表 PADDING flag 被设置
E: 一个比特位声明流的依赖性是否是排他的,存在则代表 PRIORITY flag 被设置
Stream Dependency: 指定一个 stream identifier,代表当前流所依赖的流的 id,存在则代表 PRIORITY flag 被设置
Weight: 一个无符号 8 为整数,代表当前流的优先级权重值 (1~256),存在则代表 PRIORITY flag 被设置
Header Block Fragment: header 块片段
Padding: 填充字节,没有具体语义,作用与 DATA 的 Padding 一样,存在则代表 PADDING flag 被设置
HEADERS 帧有以下标识 (flags):
END_STREAM: bit 0 设为 1 代表当前 header 块是发送的最后一块,但是带有 END_STREAM 标识的 HEADERS 帧后面还可以跟 CONTINUATION 帧 (这里可以把 CONTINUATION 看作 HEADERS 的一部分)
END_HEADERS: bit 2 设为 1 代表 header 块结束
PADDED: bit 3 设为 1 代表 Pad 被设置,存在 Pad Length 和 Padding
PRIORITY: bit 5 设为 1 表示存在 Exclusive Flag (E), Stream Dependency, 和 Weight
3.1.1.3 SETTINGS 帧格式
一个 SETTINGS 帧的 payload 由零个或多个参数组成,每个参数的形式如下:

SETTINGS Format
在建立连接开始时双方都要发送 SETTINGS 帧以表明自己期许对方应做的配置,对方接收后同意配置参数便返回带有 ACK 标识的空 SETTINGS 帧表示确认,而且连接后任意时刻任意一方也都可能再发送 SETTINGS 帧调整,SETTINGS 帧中的参数会被最新接收到的参数覆盖
SETTINGS 帧作用于整个连接,而不是某个流,而且 SETTINGS 帧的 stream identifier 必须是 0x0,否则接收方会认为错误 (PROTOCOL_ERROR)。
SETTINGS 帧包含以下参数:
SETTINGS_HEADER_TABLE_SIZE (0x1): 用于解析 Header block 的 Header 压缩表的大小,初始值是 4096 字节
SETTINGS_ENABLE_PUSH (0x2): 可以关闭 Server Push,该值初始为 1,表示允许服务端推送功能
SETTINGS_MAX_CONCURRENT_STREAMS (0x3): 代表发送端允许接收端创建的最大流数目
SETTINGS_INITIAL_WINDOW_SIZE (0x4): 指明发送端所有流的流量控制窗口的初始大小,会影响所有流,该初始值是 2^16 - 1(65535) 字节,最大值是 2^31 - 1,如果超出最大值则会返回 FLOW_CONTROL_ERROR
SETTINGS_MAX_FRAME_SIZE (0x5): 指明发送端允许接收的最大帧负载的字节数,初始值是 2^14(16384) 字节,如果该值不在初始值 (2^14) 和最大值 (2^24 - 1) 之间,返回 PROTOCOL_ERROR
SETTINGS_MAX_HEADER_LIST_SIZE (0x6): 通知对端,发送端准备接收的首部列表大小的最大字节数。该值是基于未压缩的首部域大小,包括名称和值的字节长度,外加每个首部域的 32 字节的开销
SETTINGS 帧有以下标识 (flags):
ACK: bit 0 设为 1 代表已接收到对方的 SETTINGS 请求并同意设置,设置此标志的 SETTINGS 帧 payload 必须为空
3.1.1.4 PRIORITY 帧格式

PRIORITY 帧可以在流的任何状态使用,Header帧中优先级是在打开的时候,注意区别。字段含义和header帧中的一样。PRIORITY只可作用于特定的流,不可作用于整个连接
3.1.1.5 RST_STREAM 帧格式

RST_STREAM帧用于立刻终止一个流
3.1.1.6 PUSH_PROMISE 帧格式

Pad Length: 指定 Padding 长度,存在则代表 PADDING flag 被设置
R: 保留的1bit位
Promised Stream ID: 31 位的无符号整数,代表PUSH_PROMISE 帧保留的流,对于发送者来说该流标识符必须是可用于下一个流的有效值(该标识是偶数)
Header Block Fragment: 包含请求首部域的首部块片段
Padding: 填充字节,没有具体语义,作用与 DATA 的 Padding 一样,存在则代表 PADDING flag 被设置
PUSH_PROMISE 帧有以下标识 (flags):
END_HEADERS: bit 2 置 1 代表 header 块结束
PADDED: bit 3 置 1 代表 Pad 被设置,存在 Pad Length 和 Padding
3.1.1.7 PING 帧格式

用于判断空闲连接是否可用。
PING 帧有以下标识 (flags):
ACK (0x1):设置为0表示对ping帧的回复
3.1.1.8 GOAWAY 帧格式

3.1.1.9 WINDOW_UPDATE 帧格式
WINDOW_UPDATE用于流量控制,可作用于整个连接或者流

Window Size Increment 表示除了现有的流量控制窗口之外,发送端还可以传送的字节数。取值范围是 1 到 2^31 - 1 字节
3.1.1.10 CONTINUATION 帧格式

3.2 多路复用
简而言之:多个http请求可以共用同一个TCP连接。

3.2.1 为什么http1.1不能实现多路复用
http1.1 是基于文本分割协议的。我们不知道一个请求什么时候结束,只能一直读取,直到出现空行(http请求结果标志)。所以就不能使用多路复用。要不然就不知道哪个消息是属于哪个请求了。但是HTTP2引入二进制分帧,用 stream id标识帧和请求的对应关系。
3.3 头部压缩
HTTP2使用的HPACK作为头部压缩算法。

可以清楚地看到 HTTP2 头部使用的也是键值对形式的值,而且 HTTP1 当中的请求行以及状态行也被分割成键值对,还有所有键都是小写,不同于 HTTP1。除此之外,还有一个包含静态索引表和动态索引表的索引空间,实际传输时会把头部键值表压缩,使用的算法即 HPACK,其原理就是匹配当前连接存在的索引空间,若某个键值已存在,则用相应的索引代替首部条目,比如 “:method: GET” 可以匹配到静态索引中的 index 2,传输时只需要传输一个包含 2 的字节即可;若索引空间中不存在,则用字符编码传输,字符编码可以选择哈夫曼编码,然后分情况判断是否需要存入动态索引表中。关于详细的压缩过程见参考文献10。
3.4 server push
服务端主动推送,如下图,page.html包含script.js和style.css资源文件。客户端只需要请求page.html,服务端发现page.html中包含资源文件会主动推送给客户端。减少客户端请求的次数。

所有服务器推送数据流都由 PUSH_PROMISE 帧发起,表明了服务器向客户端推送所述资源的意图,并且需要先于请求推送资源的响应数据传输。 这种传输顺序非常重要: 客户端需要了解服务器打算推送哪些资源,以免为这些资源创建重复请求。 满足此要求的最简单策略是先于父响应(即,DATA 帧)发送所有 PUSH_PROMISE 帧,其中包含所承诺资源的 HTTP 标头。
3.5 流量控制
多路复用的流会竞争 TCP 资源,进而导致流被阻塞。流控制机制确保同一连接上的流不会相互干扰。流量控制作用于单个流或整个连接。HTTP/2 通过使用 WINDOW_UPDATE 帧来提供流量控制。例如,客户端可能请求了一个具有较高优先级的大型视频流,但是用户已经暂停视频,客户端现在希望暂停或限制从服务器的传输,以免提取和缓冲不必要的数据。
流量控制是特定于连接的。两种级别的流量控制都位于单跳的端点之间,而不是整个端到端的路径。比如 server 前面有一个 front-end proxy 如 Nginx,这时就会有两个 connection,browser-Nginx, Nginx—server,flow control 分别作用于两个 connection。
流量控制是基于 WINDOW_UPDATE 帧的。接收方公布自己打算在每个流以及整个连接上分别接收多少字节。这是一个以信用为基础的方案。
流量控制是有方向的,由接收者全面控制。接收方可以为每个流和整个连接设置任意的窗口大小。发送方必须尊重接收方设置的流量控制限制。客户方、服务端和中间代理作为接收方时都独立地公布各自的流量控制窗口,作为发送方时都遵守对端的流量控制设置。
无论是新流还是整个连接,流量控制窗口的初始值是 65535 字节。
帧的类型决定了流量控制是否适用于帧。目前,只有 DATA 帧会受流量控制影响,所有其它类型的帧并不消耗流量控制窗口的空间。这保证了重要的控制帧不会被流量控制阻塞。
流量控制不能被禁用。
HTTP/2 只定义了 WINDOW_UPDATE 帧的格式和语义,并没有规定接收方如何决定何时发送帧、发送什么样的值,也没有规定发送方如何选择发送包。具体实现可以选择任何满足需求的算法。
3.6 资源优先级和依赖设置
客户端可以通过 HEADERS 帧的 PRIORITY 信息指定一个新建立流的优先级,其他期间也可以发送 PRIORITY 帧调整流优先级
每个流都可以显示地依赖另一个流,包含依赖关系表示优先将资源分配给指定的流(上层节点)而不是依赖流