一、描述
HTTP/2大致可以分为两部分:分帧层,即h2多路复用能力的核心部分;数据或http层,h2的分帧层是基于帧的二进制协议,分帧的目的,为了将重要信息都封装起来,让协议的解析方可以轻松阅读、解析并还原信息。 相比之下, h1 不是基于帧的,而是以文本分隔。通过窗口大小调节,依赖树构建,维持首部信息的静态 / 动态表,压缩 / 解压缩首部,优先级调整(h2 允许客户端多次调整单一请求的优先级), 预先推送客户端尚未请求的数据流 ,来缩短响应时间,调高页面请求渲染效率。
二、HttP2协议
2.1)连接
首先双重确认客户端支持h2,客户端会发送一个叫作connection preface(连接前奏)的魔法字节流该字节流用十六进制表示如下:0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a 解码为ASCII是:PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n 这个字符串的用处是,如果服务器(或者中间网络设备)不支持h2,就会产生一个显式错误。这个魔法字符串会有一个SETTINGS帧紧随其后。服务器为了确认它可以支持h2,会声明收到客户端的SETTINGS帧,并返回一个它自己的SETTINGS帧(反过来也需要确认),然后确认环境正常,可以开始使用h2。
如何判断H2
在连接不加密的情况下,客户端会利用 Upgrade 首部来表明期望使用 h2。如果服务器也可以支持 h2,它会返回一个“101 Switching Protocols”(协议转换)响应响应来接受升级请求。在101空内容响应终止后,服务端可以开始发送HTTP/2帧。这些帧必须包含一个发起升级的请求的响应,第一个被服务端发送的HTTP/2帧是一个设置(SETTINGS)帧。在收到101响应后,客户端发送一个包含设置(SETTINGS)帧的连接序言,为了避免不必要的延迟,允许客户端在发送客户端连接序言之后立即发送其他额外的帧,不需要等待收到服务端连接序言。不过需要注意的是,服务端连接序言设置(SETTINGS)帧可能包含一些关于期望客户端如何与服务端通信的所必须修改的参数。在收到这些设置(SETTINGS)帧之后,客户端应当遵守所有设置的参数如果不支持HTTP/2的服务端对请求返回一个不包含升级的报头字段的响应。
如 果 连 接 基 于 TLS, 情 况 就 不 同 了。 客 户 端 在 ClientHello 消 息 中 设 置 ALPN(Application-Layer Protocol Negotiation,应用层协议协商)扩展来表明期望使用 h2 协议,服务器用同样的方式回复。如果使用这种方式,那么 h2 在创建 TLS 握手的过程中完成协商。
2.2)帧
HTTP/2是基于帧 (frame)的协议。有了帧,处理协议的程序就能预先知道会收到什么。基于帧的协议,特别是h2,开始有固定长度的字节,其中包含表示整帧长度的字段,这样一来,实现和维护都会简单很多,请求和响应可以交错甚至多路复用,最大帧主体的绝对长度是 $2^{14}-1$ (16,383)字节,如果一个帧大小超过设定的限制,或者太小无法包含必须的基础帧数据,这个端点必须发送一个帧大小错误(FRAME_SIZE_ERROR)。如果帧大小错误可能修改整个连接状态,必须作为一个连接错误处理,帧的结构如下:
前9个字节对于每个帧是一致的
h2协议中有10种不同的帧类型如下:
2.3 流
可以将流看作在连接上的一系列帧,它们构成了单独的HTTP请求和响应。如果客户端想要发出请求,它会开启一个新的流。然后,服务器将在这个流上回复。这与h1的请求/响应流程类似,重要的区别在于,因为有分帧,所以多个请求和响应可以交错,而不会互相阻塞。流ID用来标识帧所属的流。客户端到服务器的h2连接建立之后,通过发送HEADERS帧来启动新的流,如果首部需要跨多个帧,可能还发会送CONTINUATION帧(更多信息参见下面的附注栏“CONTINUATIONS帧”)。该HEADERS帧可能来自HTTP请求,也可能来自响应,具体取决于发送方。后续流启动的时候,会发送一个带有递增流ID的新HEADERS帧,流内发送帧的顺序很重要。它们将按被接收的顺序处理。特别是报头及数据帧的顺序语义上是有意义的。
2.3.1 流量控制
h2提供了客户端调整传输速度的能力,WINDOW_UPDATE帧用来指示流量控制信息。每个帧告诉对方,发送方想要接收多少字节。当一端接收并消费被发送的,数据时,它将发出一个WINDOW_UPDATE帧以指示其更新后的处理字节的能力,一个新建立的流标识符必须数值大于任何终端已经打开或者保留的流标识符。规则适用于使用报头帧打开的流以及使用推送承诺帧保留的流。终端收到不规范的流标识符必须响应一个类型为协议错误(PROTOCOL_ERROR)的连接错误,流标识符不能被重复使用。生存期长的连接可能导致流标识符可用范围耗尽。客户端不能新建流标识符时可以针对新流建立一个新的连接。服务端不能新建流标识符时可以发送一个超时帧(GOAWAY)强制客户端对新的流使用新的连接。设置帧里面的SETTINGS_MAX_CONCURRENT_STREAMS参数来限制流的并发量。初始化流量控制大小是65,535字节。
流量控制是有方向性的,由接收端全权掌握。接收端可以选择针对流及整个连接设置任意的窗口大小。发送端必须遵守接收端的流量控制限制。客户端、服务端及中端代理作为接收者时都独立的向外广播他们各自的流量控制窗口,作为发送者时遵守接收端的限制。每个新的流及整个连接的流量控制窗口初始值是65,535字节,只有DATA帧受流量控制。
2.3.2 优先级
在没有多路复用的时候,在发出对新对象的请求之前,需要等待前一个响应完成。有了 h2,客户端就可以一次发出所有资源的请求,服务端也可以立即着手处理这些请求,在没有多路复用的时候,在它可以发出对新对象的请求之前,需要等待前一个响应完成。h2通过流的依赖关系来解决这个问题。通过HEADERS帧和PRIORITY帧,客户端可以明确地和服务端沟通它需要什么,以及它需要这些资源的顺序。
2.3.3 流依赖
每个流可以显式依赖其他流。包含一个依赖偏好设置表示分配资源给特定的流而不是所依赖的流,不依赖任何流的流的流依赖为0x0。
2.3.4 依赖权重
所有有依赖的流都会被分配一个1-256(含)的整数来标识权重,流的优先级是通过使用优先级帧改变的。设置一个依赖将使流变得依赖某个特定的父节点流。
2.4 服务端推送服务
如果服务器决定要推送一个对象,会构造一个PUSH_PROMISE帧,PUSH_PROMISE帧的首部块与客户端请求推送对象时发送的首部块是相似的。所以客户端有办法放心检查将要发送的请求,如果客户端对PUSH_PROMISE的任何元素不满意,就可以按照拒收原因选择重置这个流(使用RST_STREAM),或者发送PROTOCOL_ERROR(在GOAWAY帧中)。
2.5 首部压缩
HPACK(https://www.jianshu.com/p/f44b930cfcac) 是种表查找压缩方案,它利用霍夫曼编码获得接近 GZIP 的压缩效率。Hpack
2.6 请求示例
https://www.akamai.com/,www.facebook.com,www.yahoo.com,https://akah2san.h2book.com/
三、性能的影响
3.1、延迟
影响因素:端点之间的距离 和传输的介质。
3.2、丢包
如果网络中传输的数据包没有成功到达目的地,就会发生丢包;这通常是由网络拥堵造成。丢包通过丢包总数与已发送包总数的比值来衡量。频繁丢包会影响h2的页面传输,主要是因为h2开启单一TCP连接,每次有丢包/拥堵时,TCP协议就会缩减TCP窗口。
对于包含很多小型资源(365个,每个2KB)的Web页面,h2加载页面的时间比h1更短。这是因为h1下(有6个TCP连接)服务器只能并行发送6个资源(由于队头阻塞),而h2下多个流(stream)可以共用连接。进一步说,随着网络条件变差,h1和h2下PLT都会增加;但是对h2的影响更值得注意,因为这是单连接架构造成的。如果唯一的连接发生了丢包,所有工作都会受影响。
3.3、服务端推送
服务端推送让服务器具备了在客户端请求之前就推送资源的能力。测试表明,如果合理使用推送,页面渲染时间可以减少20%~50%。尽管如此,推送也会浪费带宽,这是因为服务端可能试图推送那些在客户端已经缓存的资源,导致客户端收到并不需要的数据。
3.4、第三方资源
第三方调用会让网站变慢,第三方请求往往通过不同域名发送;由于浏览器需要解析DNS、建立TCP连接、协商TLS,这将严重影响性能, 因为第三方资源在不同域名下,所以请求不能从服务端推送、资源依赖、请求优先级等h2特性中受益。这些特性仅是为请求相同域名下的资源设计的; 你无法控制第三方资源的性能,也无法决定它们是否会通过h2传输。
3.5、不利用H2性能的H1优化经验
3.5.1、域名拆分
域名拆分是为了利用浏览器对每个域名开启多个连接的能力,以便实现资源的并行下载,绕过h1的串行化下载的限制因为HTTP/2采取多路复用,所以域名拆分就不是必要的了。
3.5.2、资源内敛
资源内联包括把JavaScript、样式,甚至图片插入到HTML页面中,目的是省掉加载外部资源所需的新连接以及请求响应的时间,因为这样会损失更有价值的特性,比如缓存。
3.5.3 、资源合并
资源合并意味着把几个小文件合并成一个大文件。它与内联很相似,旨在省掉那些加载外部资源的请求响应时间,以及解码/执行那些资源所消耗的CPU资源。
3.5.4、生成精灵图
目前,生成精灵图仍是一种避免小资源请求过多的技术为了生成精灵图,开发者把较小的图片拼合成较大的图片,然后用CSS选择图片中某个部分展示出来多路复用和首部压,缩去掉了大量的请求开销。
四、实现
4.1、桌面版
如果需要建立一个新连接,而浏览器支持连接归并,那么通过复用之前已经存在的连接,就能够提升请求性能。这意味着可以跳过TCP和TLS的握手过程,改善首次请求新域名的性能。
4.2、移动端
4.3、移动端应用支持
自从2015年6月XCode更新2之后,苹果公司在iOS中提供了对h2的支持;默认情况下,在使用NSURLSession时,网络传输安全模块就会允许原生iOS应用充分利用h2协议。对于安卓应用,它们需要使用兼容h2的第三方库,比如OkHttp3,并且必须通过TLS连接到支持h2的Web服务器上。
4.4、内容分发网络
内容分发网络(CDN)是反向代理服务器的全球性分布式网络,它部署在多个数据中心。CDN的目标是通过缩短与最终用户的距离来减少请求往返次数,以此为最终用户提供高可用、高性能的内容服务,大多数主流CDN是支持HTTP/2的,虽然协议支持的完整程度以及功能特性会因厂商不同而有所差异。
五、调试
5.1、浏览器
chrome://net-internals
5. 2、webpagetest
5.3、openssl
echo | openssl s_client -connect www.tdw.cn:443 -servername www.tdw.cn
-alpn spdy/2,h2,h2-14 | grep ALPN
5.4、nghttp2
nghttp -v -n --no-dep -w 14 -a -H "Header1: Foo" https://www.akamai.com
5.5、curl
curl -v --http2 https://akah2san.h2book.com/hello-world.html -w
5.6、h2i
h2i www.google.com
5.7、Wireshark
tshark port 443 and host www.example.com