跳到主要内容

3.3-HTTP协议发展

Create by fall on 28 Aug 2021 Recently revised in 01 Dec 2023

HTTP协议发展

讲发展,先总结一下优点和缺点吧

协议优点

  • 简单:header + body 并且报文直接就是纯英文,学习门槛低,工作重心在开发应用上
  • 灵活:请求方法,都没有被限制死,拥有最大的灵活性
  • 容易扩展:和灵活差不多,请求方法、URI、状态码、原因短语、头字段等每一个核心组成要素都没有被制定死,允许开发者任意定制、扩充或解释
  • 应用广泛,它和语言没有多大关系,所以可以用用在任何支持的平台,而且因为本身的简单特性很容易实现,所以几乎所有的编程语言都有 HTTP 调用库和外围的开发测试工具。HTTP 已经延伸到了世界的每一个角落,从简单的 Web 页面到复杂的 JSON、XML 数据,从台式机上的浏览器到手机上的各种 APP、新闻、论坛、购物、手机游戏,都有 HTTP 的身影。
  • 无状态:服务器没有记忆能力,无需额外的资源来记录状态信息,会减轻服务器的负担,把更多的性能用于对外提供服务。

协议不足

  • 无状态,因为没有记忆能力,它就无法支持需要连续多个步骤的事务操作。每次都得问一遍身份信息,不仅麻烦,而且还增加了不必要的数据传输量。由此出现了 Cookie 技术。
  • 明文传输。明文意思就是协议里的报文(准确地说是 header 部分)不使用二进制数据,而是用简单可阅读的文本形式。对比 TCP、UDP 这样的二进制协议,它的优点显而易见,不需要借助任何外部工具,用浏览器、Wireshark 或者 tcpdump 抓包后,直接用肉眼就可以很容易地查看或者修改,为我们的开发调试工作带来极大的便利。缺点更为明显,就是不安全。
  • 性能:各种延迟,各种内容传输的时间都有相当大的提升空间

出现的问题发展

互联网上,平均每个页面的文件总量增加。

HTTP/1.1 的缺陷

  • 高延迟导致的页面加载速度降低

  • 队头阻塞导致带宽无法被利用(请求序列中有一个被阻塞,后面的资源也会一并阻塞)

  • 巨大的 HTTP 头部,有很多固定头字段响应报文里也有很多重复内容有上千字节,而body只有几十字节

  • 明文传输的不安全性,所有传输内容都是明文,无法确保数据安全性

  • 不支持服务器推送消息

队头阻塞解决方案:

  • chrome 对于一个域名默认允许同时建立 6 个 TCP 持久连接,能公用一个 TCP 管道,但一个管道同一时刻只能处理一个请求,没有结束前只能处于阻塞状态。如果有 10 个请求,则有 4 个进入等待状态;
  • Spriting图,通过将多个图片合称为一张大图,通过 CSS 或者 JS 切割出来
  • 将图片的原始数据嵌入在 CSS 文件 URL 中 icon{background: url(data:image/png;base64,<data>) no-repeat}
  • 将多个 JavaScript 文件通过 Webpack 打包成一个体积更大的 JavaScript 文件,但是这样会导致改变一部分 JS 代码就会重复下载整个 JS 文件。

HTTP/2协议

SPDY 协议

SPDY

Google 开发的基于 TCP 的会话层协议,主要解决 HTTP/1.1 效率不高的问题

由于背负着 HTTP/1.x 庞大的历史包袱,所以协议的修改,兼容性是首要考虑的目标,否则就会破坏互联网上无数现有的资产。

SPDY 位于 HTTP 之下,TCP 和 SSL 之上,这样可以轻松兼容老版本的 HTTP 协议(将 HTTP1.x 的内容封装成一种新的 frame 格式),同时可以使用已有的 SSL 功能。

HTTP/2

HTTP/2 基于 SPDY,专注于性能,最大的一个目标是在用户和网站间只用一个连接(connection)。目前来看,能达到 20% 到 60% 的效率提升

HTTP/2 由两个规范(Specification)组成:

  • Hypertext Transfer Protocol version 2 - RFC7540
  • HPACK - Header Compression for HTTP/2 - RFC7541

新特性

2 进制传输

而不是 HTTP/1.x 里的纯文本方式,二进制协议解析起来更高效。HTTP/2将请求和相应分为更小的帧,并采用二进制编码。

HTTP/2 中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装

Header压缩

开发了专门的"HPACK"算法,在客户端和服务器两端建立“字典”,用索引号表示重复的字符串,还采用哈夫曼编码来压缩整数和字符串,可以达到50%~90%的高压缩率。

  • 使用“首部表”跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求发送
  • “首部表”在连接存续期内始终存在,由客户端和服务器共同渐进地更新
  • 每个新的首部键值要么追加到末尾,要么替换表中之前的值

多路复用

多路复用很好的解决了浏览器限制同一域名下请求数量的问题,更容易实现全速传输,新开一个TCP连接都需要提升传输速度

HTTP2 的特性

  • 同域名下所有通信在单个连接上完成
  • 单个链接可以承载任意数量的双向数据流
  • 数据流以消息的形式发送,消息由一个或多个帧组成,多个帧在之间可以乱序发送,根据帧首部的流标识可以重新组装

性能提升:

  • 同一个域名只需要一个TCP连接(HTTP/1.1是多个文件多个TCP),是用一个连接并行发送多个请求和相应,所以只需要一次慢启动
  • 并行交错地发送多个请求/响应,请求和响应之间互不影响。
  • HTTP/2中,每个请求都可以携带一个31bit的优先值,数值越大越优先,客户端可以根据优先值采取不同策略,使用最优方式发送数据流,消息、帧

Server Push

服务器不再完全被动的响应请求,可以新建"流",主动向客户端发送消息

比如,请求HTML时就把服务器可能会用到的CSS,JS文件发送给客户端,减少等待的延迟被称为(Server Push,也叫Cache push)

客户端有权利选择是否接受,如果推送的资源已经被浏览器缓存过,可以通过发送帧RES_STREAM帧来拒收,主动推送也必须遵守同源策略。

提高安全性

处于兼容性考虑,延续了HTTP/1的“明文”的特点,可以像以前一样使用明文传输数据,不强制使用加密通信,不过格式是二进制的,只是不需要解密

主流浏览器都宣布只支持加密的HTTP/2,所以能见到的HTTP/2都是https协议名,跑在TLS上,"h2"表示加密的HTTP/2,"h2c"表示明文的HTTP/2

HTTP/2的不足

HTTP/2 解决了很多问题,但还存在一个巨大的问题,这个问题是底层支撑的TCP协议造成的

  • TCP 以及 TCP + TLS 建立连接时的延时
  • TCP 的队头阻塞并没有彻底解决

建立连接时的延时

HTTP/2 使用 TCP 协议来传输,如果使用HTTPS的话,还需要使用TLS进行安全传输,使用TLS也需要一个握手过程,就要有两个握手延迟过程

  • 建立 TCP 连接时,需要三次握手,也就是说在1.5个之后才能进行传输RTT需要1.5个往返时间,需要和服务器三次握手确定时间
  • TLS 有两个版本——TLS1.2 和 TLS1.3,每个版本建立连接所花的时间不同,大致是需要 1~2 个 RTT(Round-trip Time往返时间)。

总之,在传输数据之前,我们需要花掉 3~4 个 RTT。

队头阻塞

HTTP/2 中,多个请求跑在一个管道中,当出现丢包时,表现还不如HTTP/1。

TCP 有个丢包重传机制,丢失的包必须等待重新传输确认,而 HPPT/2 丢包时,整个 TCP 都要开始等待重传,会阻塞该 TCP 中所有请求。但对于HTTP/1 来说,可以开启多个 TCP 连接,出现该情况只会影响一个链接,剩余的还可以正常传输。

TCP 存在的时间实在太长,已经充斥在各种设备中,并且这个协议是由操作系统实现的,更新起来不大现实。

HTTP/3新特性

HTTP/3 是一个基于 UDP 协议的 QUIC(Quick UDP Internet Connection)协议,让 HTTP 跑在 QUIC 上而不是 TCP 上。

它在 HTTP/2 的基础上实现质的飞跃

QUIC的新功能

  • 实现类似 TCP 的流量控制,传输可靠性(在 UDP 上添加了一层,来保证数据可靠性传输,提供了很多 TCP 中存在的特性)
  • 快速握手功能
  • 集成了 TLS 加密功能
  • 多路复用,彻底解决了 TCP 中队头阻塞问题

连接迁移

先阐述一下tcp里面的四元组,一条tcp的唯一性标识,是由源IP,源端口,目的IP,目的端口,四元组标识。源IP,源端口一般比较稳定,但是目的IP,目的端口会由于网络元素等原因发生改变,一旦改变,那么此条tcp连接就会断开。

由于 QUIC 基于 UDP 协议,所以一条 UDP 协议不再由四元组标识,而是以客户端随机产生的一个 64 位数字作为 ID 标识。只要 ID 不变,那么这条 UDP 就会存在,维持连接,上层业务逻辑就感受不到变化。

无队头阻塞

队头阻塞问题的两层原因:

我们知道,http2.0 的多路复用正好解决了 http 层的队头阻塞,但是 tcp 的队头阻塞依然存在。因为当数据包超时确认或者丢失,会等待重传,因此会阻塞当前窗口向右滑动,造成阻塞。而QUIC 是基于 udp 的,创新点在于 QUIC 依靠一个严格的单调递增的 packet 序列,一个数据包里面还会有 streamID 和 streamoffset 偏移量,即使中途发生丢包或者超时确认,后面的数据包不会等待,等到接收完之后根据 ID 和 offset 即可完成重新拼装,从而避免了这种问题。

自定义的拥塞控制

tcp协议是在传输层,默认存在于系统中,而QUIC在应用层,当想要依据实际情况来重定义拥塞算法的时候,QUIC显然更加灵活。Google提出了cubic和newreno提供了许多可供编程的接口。当然,和tcp一样,也是默认采用cubic算法。

那么tcp的拥塞怎么控制的呢?

这一块其实知识也挺多,简单来说就是,拥塞窗口前期会指数增加,直到到达一个阈值,然后就开始线性增加,直到出现超时事件,窗口大小到达最大值MAX。之后窗口调整为初始值,开始同样的增长,阈值减小为MAX/2。但是超时不一定是因为拥塞,也可能是因为丢包,那怎么办呢?如果从最初开始增加,那么显然会比较慢,等到发送方连续收到3个接收方发出的丢包ACK,直接让窗口大小等于阈值,再线性增长。这也有一个响亮的名字,快速恢复算法。

前向安全和前向纠错

都说 UDP 不太靠谱,但是 Google 给 QUIC 加上了这个机制:每发送一组数据之后,就对这组数据进行异或运算(效率高),并将结果也发送出去,那么接收方就有两份数据版本,可以对初始数据进行纠错和校验。以此保证了可靠性。

Http2.0 的一些思考以及 Http3.0 的优势

HTTP 的协议的发展也是经过了一个比较漫长的阶段,从最初的只支持 get 请求和纯文本的返回内容,到现在的各种文件,超文本的传输,http 协议毫无疑问越来越强大。

本文着重于1.0,2.0,3.0的版本的差异以及一些深层的原因。先从 2.0 开始

二进制分帧

先解释一下,就是将一条连接上所有传输的信息,分割为更小的消息和帧(消息则是由一个或者多个帧组成的),并对他们采用二进制格式编码。首部信息放在 Headers 帧中,而主体信息被封装在 Data 帧中。而且在每个帧的首部都有一个标识位。那么问题就来了。

为什么 2.0 可以对所有的内容进行二进制转换?

因为二进制分帧层是在应用层和传输层之间的中间层,所有的信息都会从中经过,进而可以转换。

为什要用二进制?

首先就是效率会更高,计算机最喜欢处理二进制数了。除此之外就是可以根据帧头部的八个位来定义额外的帧。除了数据帧和头部帧,实际上还有 PING 帧、SETTING 帧、优先级帧等等,为之后的多路复用打上坚实的基础。

有什么其他的好处?

还可以在一个连接上实现双向数据流以及乱序发送。因为在,每一个帧上都有一个标记位。浏览器和服务端双方可以前期乱序接收消息和帧。接收完毕按照标记位的排列来拼接成一整条信息。所以,浏览器并行发送的请求,服务器可以并行返回,而不需要按照顺序返回。

多路复用

简单来说,多路复用技术也是可以并行发送请求,而且无需等待响应返回的一种技术。消除了不必要的延迟,减少页面的加载时间。

和 1.0 的长连接的区别在哪?

1.0默认开启长连接,也就是保持tcp的连接不中断,可以一直发送http请求。但是长连接只能发送串行的请求,也就是一问一答式的,如果前一个请求的响应没有被接收,那么第二个请求不会发送,就会造成阻塞。而多路复用就是在一条tcp连接上,请求可以并行发送,而无需等待前面的响应返回。

和 1.0 的管道的区别?

管道也可以并行发送请求,但是返回响应的顺序则必须是发送时候的顺序。例如,发送A,B,C三个请求,那么返回的顺序就是A,B,C哪怕A返回之前,B,C已经准备好,依然要等到A返回,也容易造成阻塞。

实际上,多路复用的基础就是二进制分帧,因为可以乱序发送和接收,所以就不必担心接收错误消息的问题,接收完毕直接拼接。

首部压缩

首部压缩实现的一个核心预设就是,在第一次请求之后,大部分的字段可以复用的。而且随着页面越来越复杂,同一个页面发出的请求会越来越多。如果头部不压缩的话,会造成很大的流量开销。

首部压缩的原理?

支持 http2.0 的浏览器和服务器会维护一个相同的静态表和一个动态表,以及内置一个霍夫曼编码表。静态表存储的是常见的一些头部,和一些很常见的头部键值对,例如 method:get 以及 cookie。动态表开始是空的,如果头部命中静态表中的名称,那么就回将这份键值对加入动态表中,例如cookie:xxxx。这样做的原因在于,请求或则响应头命中了静态或者动态表的时候,只需要一个字节就能表示,可想而知,这个字节就是一个地址,指向表中的数据。来张大佬的图或许更加清晰。

而且像 cookie 对应的值可以由霍夫曼编码,来压缩体积。本质上,还是基于二进制分帧的数据转换。

如果静态和动态表中都没有相应的字段怎么办

可以知道的是,键和值都是长度不确定的,所以两者都提倡采用霍夫曼编码,而且在帧流中,都是以01000000作为起始字节作为标识,这是一个“外来家伙”。添加到动态表中。

请求优先级

客户端可以在发出的 Headers 帧中包含优先次序信息来为流指定优先级。也可以专门在 Priority 帧来改变流的优先级。具体实现就,是每个流都可以带有一个 31 比特的优先值:0 表示最高优先级;231 -1 表示最低优先级。而服务器则根据优先级作为交互数据的依据,在响应数据准备好之后,优先将最高优先级的帧发送给客户端。

高优先级的流一定得排在低优先级流的前面吗

不一定。高优先级的流不是绝对先发出。因为强制的规定可能会造成阻塞:低优先级的流会等待高优先级的流。

常见的高优先级的流是哪些?

  • 优先级最高:主要的 html
  • 优先级高:CSS 文件
  • 优先级中:js 文件
  • 优先级低:图片/视频

服务器推送

简单来说,服务器可以对一个客户端请求发送多个响应,例如,浏览器向服务端请求index.html,里面包含一张样式表和一张图片。传统的方法就是会向浏览器发送三次请求。而服务端推送,则可以在一次请求内将这三个文件全部发送给浏览器,减少了请求次数,提升了网页性能。下面是网上关于 http2.0 的性能提升的,比 1.0 里面的将资源内嵌到网页中都要高。

不同域之间可以推送吗?

不可以。服务端推送也是遵守同源策略。

服务端推送有什么弊端?

如果服务端推送的内容,浏览器有缓存的话,就会浪费带宽。避免的方法就是在服务端配置,只对第一次请求实现服务器的推送。

好了,至此 Http2.0 的优势可以说是比 Http1.0 进步了许多。可以看到,2.0里面的大部分功能都是建立在二进制分帧这个基础的技术上,所以或许这也是他被称作 2.0 的理由吧。但是问题来了,Http2.0 就是无懈可击了吗?还有没有他无法解决或者回避解决的问题?答案是肯定的。接下来来到Http3.0 的神奇世界,看一看降维打击。

Http3.0 相对于 Http2.0 是一种脱胎换骨的改变!

大家都知道,http 协议是应用层协议,都是建立在传输层之上的。我们也都知道传输层上面不只有 TCP 协议,还有另外一个强大的协议 UDP 协议,2.0 和 1.0 都是基于 TCP 的,因此都会有 TCP 带来的硬伤以及局限性。而 Http3.0 则是建立在 UDP 的基础上。所以其与 Http2.0 之间有质的不同。

Google 率先推出的 QUIC(Quick UDP Internet Connection)就是很好的实践。

协议发展

性能优化

HTTP1

  • HTTP/0.9 基于 TCP 协议,三次握手建立连接,发送一个 GET 请求行(没有请求头和请求体),服务器接收请求之后,读取对应 HTML 文件,数据以 ASCII 字符流返回,传输完成断开连接;
  • HTTP/1.0 增加请求头和响应头来进行协商,在发起请求时通过请求头告诉服务器它期待返回什么类型问题、什么形式压缩、什么语言以及文件编码。引入来状态吗,Cache 机制等;
  • HTTP/1.1 改进持久化连接,解决建立 TCP 连接、传输数据和断开连接带来的大量开销,支持在一个 TCP 连接上可以传输多个 HTTP 请求,目前浏览器对于一个域名同时允许建立 6 个 TCP 持久连接;
  • HTTP/1.1 引入 Chunk transfer 支持动态生成内容:服务器将数据分割成若干任意大小的数据块,每个数据块发送时附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志。在 HTTP/1.1 需要在响应头中设置完整的数据大小,如 Content-Length。

提升网络速度

  • HTTP/1.1 主要问题:TCP 慢启动;同时开启多条 TCP 连接,会竞争固定宽带;对头阻塞问题;
  • HTTP/2 在一个域名下只使用一个 TCP 长连接和消除对头阻塞问题;
  • 多路复用的实现:HTTP/2 添加了二进制分帧层,将发送或响应数据经过二进制分帧处理,转化为一个个带有请求 ID 编号的帧,服务器或者浏览器接收到响应帧后,根据相同 ID 帧合并为一条完整信息;
  • 设置请求优先级:发送请求可以设置请求优先级,服务器可以优先处理;
  • 服务器推送:请求一个 HTML 页面,服务器可以知道引用了哪些 JavaScript 和 CSS 文件,附带一起发送给浏览器;
  • 头部压缩:对请求头和响应头进行压缩;

HTTP3

甩掉 TCP、TCL 包袱,构建高效网络

  • 虽然 HTTP/2 解决了应用层面的对头阻塞问题,不过和 HTTP/1.1 一样,HTTP/2 依然是基于 TCP 协议,而 TCP 最初是为了单连接而设计;
  • TCP 可以看成是计算机之间的一个虚拟管道,数据从一端发送到另一端会被拆分为一个个按照顺序排列的数据包,如果在传输过程中,有一个数据因为网络故障或者其他原因丢失,那么整个连接会处于暂停状态,只有等到该数据重新传输;
  • 由于 TCP 协议僵化,也不可能使用新的协议,HTTP/3 选择了一个折衷的方法,基于现有的 UDP 协议,实现类似 TC 片多路复用,传输可靠等功能,称为 QULC 协议;
  • QULC 实现类似 TCP 流量控制,传输可靠功能;集成 TLS 加密功能;实现多路复用功能;

参考文章

作者链接
程序员cxuanhttps://juejin.cn/post/6844904045572800525