面试官:http/1.0和http/2.0区别是什么?css
你:html
解决 HTTP 中的队头阻塞问题;前端
二进制协议
面试
http/2.0 的首部还会被深度压缩。这将显著减小传输中的冗余字节
算法
多路复用
promise
你觉得你备几条答案就完事了吗🤡,咱能不能有点出息,稍微详细一点能够不?又没让你现场调试http/2.0慌个毛线啊~🗣
老规矩啊,一会儿没看懂的,或者脑壳放空不能吸取知识的时候,别来打我啊,请你收藏下来慢慢看, 耐心!!! 耐心!!! 耐心!!!
🎄http/0.9 :只支持get方法,不支持多媒体内容的 MIME 类型、各类 HTTP 首部,或者版本号,只是为了获取html对象。浏览器
🌸http/1.0 :添加了版本号、各类 HTTP 首部、一些额外的方法,以及对多媒体对象的处理。缓存
🍄http/1.0+ :keep-alive 链接、虚拟主机支持,以及代理链接支持都被加入到 HTTP 之中等等。安全
🍁http/1.1: 重点关注的是校订 HTTP 设计中的结构性缺陷,明确语义,引入重要 的性能优化措施,并删除一些很差的特性:如Entity tag,If-Unmodified-Since, If-Match, If-None-Match;请求头引入了range头域,它容许只请求资源的某个部分(206);新增了更多的状态码;Host头处理(400)性能优化
🌺HTTP/2.0 被寄予了以下指望:
相比于使用 TCP 的 HTTP/1.1,最终用户可感知的多数延迟都有可以量化的显 著改善;
解决 HTTP 中的队头阻塞问题;
并行的实现机制不依赖与服务器创建多个链接,从而提高 TCP 链接的利用率,
特别是在拥塞控制方面;
保留 HTTP/1.1 的语义,能够利用已有的文档资源(如上所述),包括(但不限于)
HTTP 方法、状态码、URI 和首部字段;
明肯定义 HTTP/2.0 和 HTTP/1.x 交互的方法,特别是经过中介时的方法(双向);
明确指出它们能够被合理使用的新的扩展点和策略。
http/2 大体能够分为两部分:分帧层,即 h2 多路复用能力的核心部分;数据或 http 层,其中包含传统上被认为是 HTTP 及其关联数据的部分。
二进制协议:
h2 的分帧层是基于帧的二进制协议。这方便了机器解析,可是肉眼识别起来比较困难。
首部压缩:
仅仅使用二进制协议彷佛还不够,h2 的首部还会被深度压缩。这将显著减小传输中的冗余字节
多路复用 :
在你喜好的调试工具里查看基于 h2 传输的链接的时候,你会发现请求和响应交织在一块儿。
加密传输:
线上传输的绝大部分数据是加密过的,因此在中途读取会更加困难。
链接是全部 HTTP/2 会话的基础元素,其定义是客户端初始化的一个 TCP/IP socket,客户端 是指发送 HTTP 请求的实体。这和 h1 是同样的,不过与彻底无状态的 h1 不一样的是,h2 把它所承载的帧(frame)和流(stream)共同依赖的链接层元素捆绑在一块儿,其中既包含链接层设置也包含首部表。
判断是否支持http/2.0
🍏协议发现——识别终端是否支持你想使用的协议——会比较棘手。HTTP/2 提供两种协
议发现的机制。
🍐在链接不加密的状况下,客户端会利用 Upgrade 首部来代表指望使用 h2。若是服务器 也能够支持 h2,它会返回一个“101 Switching Protocols”(协议转换)响应。这增长了 一轮完整的请求-响应通讯。
🍑 若是链接基于 TLS,状况就不一样了。客户端在 ClientHello 消息中设置 ALPN (Application-Layer Protocol Negotiation,应用层协议协商)扩展来代表指望使用 h2 协 议,服务器用一样的方式回复。
为了向服务器双重确认客户端支持 h2,客户端会发送一个叫做 connection preface(链接 前奏)的魔法字节流,做为链接的第一份数据。这主要是为了应对客户端经过纯文本的 HTTP/1.1 升级上来的状况。该字节流用十六进制表示以下:
0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a复制代码
解码为 ASCII 是:
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n 复制代码
这个字符串的用处是,若是服务器(或者中间网络设备)不支持 h2,就会产生一个显式错误。这个消息特地设计成 h1 消息的样式。若是运行良好的 h1 服务器收到这个字符串,它会阻塞这个方法(PRI)或者版本(HTTP/2.0),并返回错误,可让 h2 客户端明确地知道发生了什么错误。
这个魔法字符串会有一个 SETTINGS 帧紧随其后。服务器为了确认它能够支持 h2,会声明收到客户端的 SETTINGS 帧,并返回一个它本身的 SETTINGS 帧(反过来也须要确认), 而后确认环境正常,能够开始使用 h2。 (注意⚠️:SETTINGS 帧是个很是重要的帧,http/2.0有十几个帧,不一样的帧表明不同的状态功能)
HTTP/2 是基于帧(frame)的协议。采用分帧是为了将重要信息都封装起来,让协议的解析方能够轻松阅读、解析并还原信息。 相比之下,h1 不是基于帧的,而是以 文本分隔。 看看下面的简单例子:
GET / HTTP/1.1 <crlf>
Host: www.example.com <crlf>
Connection: keep-alive <crlf>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9... <crlf>
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4)... <crlf>
Accept-Encoding: gzip, deflate, sdch <crlf>
Accept-Language: en-US,en;q=0.8 <crlf>
Cookie: pfy_cbc_lb=p-browse-w; customerZipCode=99912|N; ltc=%20;...<crlf> 复制代码
解析这种数据用不着什么高科技,但每每速度慢且容易出错。你须要不断读入字节,直到 遇到分隔符为止(这里是指 <crlf>),同时还要考虑一些不太守规矩的客户端,它们会只 发送 <lf>。
解析 h1 的请求或响应可能出现下列问题:
• 一次只能处理一个请求或响应,完成以前不能中止解析。
• 没法预判解析须要多少内存。这会带来一系列问题:你要把一行读到多大的缓冲区里。
若是行太长会发生什么;应该增长并从新分配内存,仍是返回 400 错误。为了解决这些问题,保持内存处理的效率和速度可不简单。
从另外一方面来讲,有了帧,处理协议的程序就能预先知道会收到什么。基于帧的协议,特别是 h2,开始有固定长度的字节,其中包含表示整帧长度的字段。
来,咱们看一下帧结构🤓
由于规范严格明确,因此解析逻辑大概是这样:
loop
Read 9 bytes off the wire //读前9个字节
Length = the first three bytes //长度值为前3字节
Read the payload based on the length. //基于长度读负载
Take the appropriate action based on the frame type. // 根据帧类型采起对应操做
end loop 复制代码
http/1有个特性叫管道化(pipelining),容许一次发送一组请求,可是只能按照发送顺序依次接 收响应。并且,管道化备受互操做性和部署的各类问题的困扰,基本没有实用价值。 在请求应答过程当中,若是出现任何情况,剩下全部的工做都会被阻塞在那次请求应答之 后。这就是“队头阻塞”.它会阻碍网络传输和 Web 页面渲染,直至失去响应。为了防止 这种问题,现代浏览器会针对单个域名开启 6 个链接,经过各个链接分别发送请求。
它实 现了某种程度上的并行,可是每一个链接仍会受到“队头阻塞”的影响。 因为 h2 是分帧的, 请求和响应能够交错甚至多路复用。多路复用有助于解决相似队头阻塞的问题。
我怎么会让大家一脸懵逼的去百度呢,来来来,往下看
借老哥一张图: Head of line blocking
HTTP/2 规范对流(stream)的定义是:“HTTP/2 链接上独立的、双向的帧序列交换。”你能够将流看做在链接上的一系列帧,它们构成了单独的 HTTP 请求和响应。若是客户端想 要发出请求,它会开启一个新的流。而后,服务器将在这个流上回复。这与 h1 的请求 / 响应流程相似,重要的区别在于,由于有分帧,因此多个请求和响应能够交错,而不会互相阻塞。
看到这里,是否是开始对以前看到的帧,多路复用模糊了,怎么又来了个流?阿西吧,别慌,让我给你解释解释🤨
流(stream),一个完整的请求-响应数据交互过程,具备以下几个特色:
流的建立:流能够被客户端或服务器单方面创建, 使用或共享;
流的关闭:流也能够被任意一方关闭;
敲黑板,画重点!!!别再混淆了!
多路复用:一个链接同一时刻能够被多个流使用。
流的并发性:某一时刻,链接上流的并发数。
借老哥一张图: http/2.0流
咱再强调一下多路复用的好处:
链接、流和帧的关系
HTTP 消息泛指 HTTP 请求或响应。 流是用来传输一对请求 / 响 应消息的。一个消息至少由 HEADERS 帧(它初始化流)组成,而且能够另外包含 CONTINUATION 和 DATA 帧,以及其余的 HEADERS 帧。
h1 的请求和响应都分红消息首部和消息体两部分;与之相似,h2 的请求和响应分红HEADERS 帧和 DATA 帧。
h1 把消息分红两部分:请求 / 状态行;首部。h2 取消了这种区分,并把这些行变成了 魔法伪首部。举个例子,HTTP/1.1 的请求和响应多是这样的:
GET / HTTP/1.1
Host: www.example.com
User-agent: Next-Great-h2-browser-1.0.0
Accept-Encoding: compress, gzip
HTTP/1.1 200 OK
Content-type: text/plain
Content-length: 2 ...复制代码
在 HTTP/2 中,它等价于:
:scheme: https:method: GET
:path: /
:authority: www.example.com
User-agent: Next-Great-h2-browser-1.0.0
Accept-Encoding: compress, gzip
:status: 200
content-type: text/plain复制代码
请注意,请求和状态行在这里拆分红了多个首部,即 :scheme、:method、:path 和 :status。 同时要注意的是,http/2.0 的这种表示方式跟数据传输时不一样。
没有分块编码(chunked encoding)
在基于帧的世界里,谁还须要分块?只有在没法预先知道数据长度的状况下向对方发送 数据时,才会用到分块。在使用帧做为核心协议的 h2 里,就再也不须要它了。
再也不有101的响应
Switching Protocol 响应是 h1 的边缘应用。它现在最多见的应用可能就是用以升级到 WebSocket 链接。ALPN 提供了更明确的协议协商路径,往返的开销也更小。
其实如今大部分仍是都是 http/1.1, http/2.0网站很少。还好日常刷 leetcode,发现 leetcode不只用 http/2.0,还用 graphql,走在科技的前沿🤪
再来观摩一下人家的graphql,想起来前段时间组里叫我实现前端graphql,如今想一想就是一把辛酸泪😭
今天重点是http/2.0,就很少说graphql。有兴趣老哥请点击传送门。
h2 的新特性之一是基于流的流量控制。不一样于 h1 的世界,只要客户端能够处理,服务端就会尽量快地发送数据,h2 提供了客户端调整传输速度的能力。(而且,因为在 h2 中, 一切几乎都是对称的,服务端也能够调整传输的速度。)WINDOW_UPDATE 帧用来指示流量控制信息。每一个帧告诉对方,发送方想要接收多少字节。
客户端有不少理由使用流量控制。一个很现实的缘由多是,确保某个流不会阻塞其余流。也可能客户端可用的带宽和内存比较有限,强制数据以可处理的分块来加载反而能够提高效率。尽管流量控制不能关闭,把窗口最大值设定为设置 2^31-1 就等效于禁用它,至少对小于 2GB 的文件来讲是如此。
另外一个须要注意的是中间代理。一般状况下,网络内容经过代理或者 CDN 来传输,也许它们就是传输的起点或终点。因为代理两端的吞吐能力可能不一样,有了流量控制,代理的两端就能够密切同步,把代理的压力降到最低。
流的最后一个重要特性是依赖关系。现代浏览器都通过了精心设计,首先请求网页上最重要的元素,以最优的顺序获取资源,由此来优化页面性能。拿到了 HTML 以后,在渲染页面以前,浏览器一般还须要 CSS 和关键 JavaScript 这样的东西。在没有多路复用的时候,在它能够发出对新对象的请求以前,须要等待前一个响应完成。有了 h2,客户端就能够 一次发出全部资源的请求,服务端也能够当即着手处理这些请求。由此带来的问题是,浏览器失去了在 h1 时代默认的资源请求优先级策略。假设服务器同时接收到了 100 个请求, 也没有标识哪一个更重要,那么它将几乎同时发送每一个资源,次要元素就会影响到关键元素 的传输。
h2 经过流的依赖关系来解决这个问题。经过 HEADERS 帧和 PRIORITY 帧,客户端能够明确地和服务端沟通它须要什么,以及它须要这些资源的顺序。这是经过声明依赖关系树 和树里的相对权重实现的。
• 依赖关系为客户端提供了一种能力,经过指明某些对象对另外一些对象有依赖,告知服务器这些对象应该优先传输。
• 权重让客户端告诉服务器如何肯定具备共同依赖关系的对象的优先级。
咱们来看看这个简单的网站:
index.html
– header.jpg
– critical.js
– less_critical.js
– style.css
– ad.js
– photo.jpg 复制代码
在收到主体 HTML 文件以后,客户端会解析它,并生成依赖树,而后给树里的元素分配权重。这时这棵树多是这样的:
index.html
– style.css
– critical.js
– less_critical.js (weight 20)
– photo.jpg (weight 8)
– header.jpg (weight 8)
– ad.js (weight 4) 复制代码
在这个依赖树里,客户端代表它最须要的是 style.css,其次是 critical.js。没有这两个文件, 它就不能接着渲染页面。等它收到了 critical.js,就能够给出其他对象的相对权重。权重表示服务一个对象时所须要花费的对应“努力”程度。
提高单个对象性能的最佳方式,就是在它被用到以前就放到浏览器的缓存里面。这正是 HTTP/2 的服务端推送的目的。推送使服务器可以主动将对象发给客户端,这多是由于 它知道客户端不久将用到该对象。
若是服务器决定要推送一个对象(RFC 中称为“推送响应”),会构造一个 PUSH_PROMISE 帧。这个帧有不少重要属性,列举以下 :
🐢PUSH_PROMISE 帧首部中的流 ID 用来响应相关联的请求。推送的响应必定会对应到 客户端已发送的某个请求。若是浏览器请求一个主体 HTML 页面,若是要推送此页面 使用的某个 JavaScript 对象,服务器将使用请求对应的流 ID 构造 PUSH_PROMISE 帧。
🦑PUSH_PROMISE 帧的首部块与客户端请求推送对象时发送的首部块是类似的。因此客户端有办法放心检查将要发送的请求。
🐞被发送的对象必须确保是可缓存的。
🐙:method 首部的值必须确保安全。安全的方法就是幂等的那些方法,这是一种不改变
任何状态的好办法。例如,GET 请求被认为是幂等的,由于它一般只是获取对象,而POST 请求被认为是非幂等的,由于它可能会改变服务器端的状态。
🐳理想状况下,PUSH_PROMISE 帧应该更早发送,应当早于客户端接收到可能承载着推
送对象的 DATA 帧。假设服务器要在发送 PUSH_PROMISE 以前发送完整的 HTML, 那客户端可能在接收到 PUSH_PROMISE 以前已经发出了对这个资源的请求。h2 足够健壮,能够优雅地解决这类问题,但仍是会有些浪费。
🐡PUSH_PROMISE 帧会指示将要发送的响应所使用的流 ID。
客户端会从 1 开始设置流 ID,以后每新开启一个流,就会增长 2,以后一直 使用奇数。服务器开启在 PUSH_PROMISE 中标明的流时,设置的流 ID 从 2 开始,以后一直使用偶数。这种设计避免了客户端和服务器之间的流 ID 冲 突,也能够轻松地判断哪些对象是由服务端推送的。0 是保留数字,用于连 接级控制消息,不能用于建立新的流。
若是客户端对 PUSH_PROMISE 的任何元素不满意,就能够按照拒收缘由选择重置这个流 (使用 RST_STREAM),或者发送 PROTOCOL_ERROR(在 GOAWAY 帧中)。常见的状况是缓存中已经有了这个对象。而 PROTOCOL_ERROR 是专门留给 PUSH_PROMISE 涉及的协议层面问题的,好比方法不安全,或者当客户端已经在 SETTINGS 帧中代表本身不接受推送时,仍然进行了推送。值得注意的是,服务器能够在 PUSH_PROMISE 发送后当即启动推送流,所以拒收正在进行的推送可能仍然没法避免推送大量资源。推送正确的资源是不够的,还须要保证只推送正确的资源,这是重要的性能优化手段。
因此到底如何选择要推送的资源?
决策的过程须要考虑到以下方面:
• 资源已经在浏览器缓存中的几率
• 从客户端看来,这些资源的优先级
• 可用的带宽,以及其余相似的会影响客户端接收推送的资源
若是用户第一次访问页面时,就能向客户端推送页面渲染所需的关键 CSS 和 JS 资源,那 么服务端推送的真正价值就实现了。不过,这要求服务器端实现足够智能,以免“推送 承诺”(push promise)与主体 HTML 页面传输竞争带宽。
“臃肿的消息首部”提到过,现代网页平均包含 140 个请求,每一个 HTTP 请求 平均有 460 字节,总数据量达到 63KB。即便在最好的环境下,这也会形成至关长的延时, 若是考虑到拥挤的 WiFi 或链接不顺畅的蜂窝网络,那但是很是痛苦的。这些请求之间一般几乎没有新的或不一样的内容,这才是真正的浪费。因此,你们迫切渴望某种类型的压缩。 通过屡次创新性的思考和讨论,人们提出了 HPACK。HPACK 是种表查找压缩方案,它利用霍夫曼编码得到接近 GZIP 的压缩率。
CRIME 攻击告诉咱们, GZIP 也有泄漏加密信息的风 险。 CRIME 的原理是这样的,攻击者在请求中添加数据,观察压缩加密后的数据量是否会小于预期。若是变小了,攻击者就知道注入的文本和请求中的其余内容(好比私有的 会话 cookie)有重复。在很短的时间内,通过加密 的数据内容就能够所有搞清楚。所以,你们放弃了已有的压缩方案,研发出 HPACK。
第一个请求:
:authority: www.akamai.com
:method: GET
:path: /
:scheme: https
accept: text/html,application/xhtml+xml
accept-language: en-US,en;q=0.8
cookie: last_page=286A7F3DE
upgrade-insecure-requests: 1
user-agent: Awesome H2/1.0复制代码
第二个请求:
:authority: www.akamai.com
:method: GET :path: /style.css :scheme: https accept: text/html,application/xhtml+xml accept-language: en-US,en;q=0.8 cookie: last_page=*398AB8E8F upgrade-insecure-requests: 1 user-agent: Awesome H2/1.0
复制代码
能够看到,后者的不少数据与前者重复了。第一个请求约有 220 字节,第二个约有 230 字 节,但两者只有 36 字节是不一样的。若是仅仅发送这 36 字节,就能够节省约 85%的字节 数。简而言之,HPACK 的原理就是这样。
h1 下的一些性能调优办法在 h2 下会起到副作用。
域名拆分是为了利用浏览器对每一个域名开启多个链接的能力,以便实现资源的并行下载, 绕过 h1 的串行化下载的限制。对于包含大量小型资源的网站,广泛的作法是拆分域名, 以利用现代浏览器针能对每一个域名开启 6 个链接的特性。这样实际上作到了让浏览器并行 发送多个请求,以及充分利用可用带宽的效果。由于 HTTP/2 采起多路复用,因此域名拆 分就不是必要的了,而且反而会让协议力图实现的目标落空。
资源内联包括把 JavaScript、样式,甚至图片插入到 HTML 页面中,目的是省掉加载外部资源所需的新链接以及请求响应的时间。然而,有些 Web 性能的最佳实践不推荐使用内联,由于这样会损失更有价值的特性,好比缓存。若是有同一个页面上的重复访问,缓存一般能够减小请求数(并且可以加速页面渲染)。尽管如此,整体来讲,对那些渲染滚动 条以上区域所需的微小资源进行内联处理还是值得的。
事实上有证据代表,在性能较弱的 设备上,缓存对象的好处不够多,把内联资源拆分出来并不划算。 使用 h2 时的通常原则是避免内联,可是内联也并不必定毫无价值。
资源合并意味着把几个小文件合并成一个大文件。它与内联很类似,旨在省掉那些加载外部资源的请求响应时间,以及解码 / 执行那些资源所消耗的 CPU 资源。以前针对资源内联 的规则一样适用于资源合并,咱们可使用它来合并不是常小的文件(1KB 或更小),以及 对初始渲染很关键的最简化 JavaScript/CSS 资源。
经过禁用 cookie 的域名来提供静态资源是一项标准的性能优化最佳实践。尤为是使用 h1 时,你没法压缩首部,并且有些网站使用的 cookie 大小经常超过单个 TCP 数据包的限度。 不过,在 h2 下请求首部使用 HPACK 算法被压缩,会显著减小巨型 cookie(尤为是当它 们在前后请求之间保持不变)的字节数。与此同时,禁用 cookie 的域名须要额外的主机名 称,这意味着将开启更多的链接。
若是你正在使用禁用 cookie 的域名,之后有机会你可能得考虑消灭它。若是你确实不须要那些域名,最好删掉它们。省一个字节就是一个字节。
目前,生成精灵图还是一种避免小资源请求过多的技术(你能看到人们乐意作什么来优化 h1)。为了生成精灵图,开发者把较小的图片拼合成较大的图片,而后用 CSS 选择图片中 某个部分展现出来。依据设备及其硬件图形处理能力的不一样,精灵图要么很是高效,要么 很是低效。若是用 h2,最佳实践就是避免生成精灵图;主要缘由在于,多路复用和首部压缩去掉了大量的请求开销。即使如此,仍是有些场景适合使用精灵图。
为了最大化 Web 性能,须要在许多变量之间取舍,包括网络条件、设备 处理能力、浏览器能力,还有协议限制。这些组成了咱们所说的场景,大多数开发者 的时间远远不够考虑那么多场景。
怎么办?最佳实践的第一原则就是:测试。性能测试与监控是得到最大成果的关键, HTTP/2 也不例外。观察真实用户数据、详尽分析各类条件、查找问题,而后解决它们。要遵循业界推荐的方式,但也不要陷入过早优化的陷阱。
it's over.
有问题,欢迎指正呀~