likes
comments
collection
share

聊一聊 HTTP 发展史

作者站长头像
站长
· 阅读数 18

Http/0.9

HTTP协议是应用层的协议,http/0.9是在1991年提出,需求简单,只需要进行HTML文件的传输,下面是一个完整请求流程

  • http基于tcp/ip协议,进行三次握手,建立连接
  • 发送Get请求行信息,如:GET /index.html
  • 服务器端发送以ASCII字符流的形式给客户端
  • 四次挥手断开连接

聊一聊 HTTP 发展史 因为互联网发展迅速,产生了以下几个问题:

  1. 传输的文件不单单是HTML文件了
  2. 传输的文件的数据量越来越大了
  3. 支持国际化,要选择语言版本
  4. 文件类型不同,编码的类型又可能不同
  5. 有的请求可能会出现问题,需要告诉浏览器服务器处理后的结果
  6. 怎么减轻服务器压力
  7. 怎么知道用户的基础信息

Http/1.0

前四个问题,是通过请求头和响应头协商来解决的。

发起请求时候会通过 HTTP 请求头告诉服务器它期待服务器返回什么类型的文件、采取什么形式的压缩、提供什么语言的文件以及文件的具体编码。最终发送出来的请求头内容如下:

accept: text/html # 类型文件
accept-encoding: gzip, deflate, br # 压缩方式
accept-Charset: ISO-8859-1,utf-8 # 文件的编码方式
accept-language: zh-CN,zh # 优先语言

服务器接收到浏览器发送过来的请求头信息之后,下面是一段响应头的数据信息:

content-encoding: br #告诉浏览器最终的压缩类型
content-type: text/html; charset=UTF-8 # 文件类型、编码类型

后面三个问题的解决方式:

  • 状态码
  • Cache机制
  • 在请求头中增加了用户代理字段

又出现了几个问题:

  1. 单个页面的图片文件越来越多,频繁建立 TCP 连接、传输数据和断开连接这样的步骤,无疑会增加大量无谓的开销
  2. Http协议是无状态的协议,需要进行会话信息的存储,方便用户体验和减轻服务器端压力
  3. 如果 TCP 通道中的某个请求因为某些原因没有及时返回,那么就会阻塞后面的所有请求,这就是著名的队头阻塞的问题
  4. 很多页面都是动态生成的,所需要的数据的内容大小在传输前不知道大小,导致了浏览器不知何时才可以接收完所有数据
  5. 传输数据的安全机制也越来越重要

http/1.1

解决方式:

  • 增加了持久连接的方法,只要没有明确断开,就会保持连接,可以发送多个http请求,在请求行里设置:
Connection:keep-alive
  • 增加了cookie进行存储会话信息
  • 进行了管线化的尝试,最后还是失败了,因为即使可以整批发送请求,但服务器还是得进行一个一个的处理,无法解决队头阻塞的问题
  • 虚拟主机运行在同一个 IP 上,因此使用首部字段Host加以 区分。首部字段 Host 会告知服务器,请求的资源所处的互联网主机名和端口号
  • 引入 Chunk transfer 机制来解决这个问题,服务器会将数据分割成若干个任意大小的数据块,每个数据块发送时会附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志。这样就提供了对动态内容的支持。
  • 引入了Https协议来进行提供安全的通道

不仅如此,http/1.1在为网络效率做了很大的优化:

  1. 持久连接
  2. 浏览器为每个域名最多同时维护6个TCP连接
  3. 使用 CDN 的实现域名分片机制,将一个页面的资源利用多个域名下载,提高tcp并发数量(http2中已不需要)

但是出现的问题是对宽带的利用率不高,很难用满,之所以会出现这个问题,有下面几个方面:

  • TCP的慢启动
    • 最初由 V. Jacobson 在1988年的论文中提出的 TCP 的拥塞控制由“慢启动(Slow start)”和“拥塞避免(Congestion avoidance)”组成,后来 TCP Reno 版本中又针对性的加入了“快速重传(Fast retransmit)”、“快速恢复(Fast Recovery)”算法,再后来在 TCP NewReno 中又对“快速恢复”算法进行了改进,近些年又出现了选择性应答(selective acknowledgement,SACK)算法
  • 同时开启了多条 TCP 连接,那么这些连接会竞争固定的带宽
    • 因为有的 TCP 连接下载的是一些关键资源,如 CSS 文件、JavaScript 文件等,而有的 TCP 连接下载的是图片、视频等普通的资源文件,但是多条 TCP 连接之间又不能协商让哪些关键资源优先下载,这样就有可能影响那些关键资源的下载速度了。
  • 队头阻塞的问题

http/2.0

想办法去规避 TCP 的慢启动和 TCP 连接之间的竞争问题,还有http1.1的队头阻塞问题:

  • 一个域名只使用一个 TCP 长连接和消除队头阻塞问题,HTTP/2 最核心、最重要且最具颠覆性的多路复用机制,HTTP/2 使用了多路复用技术,可以将请求分成一帧一帧的数据去传输,这样带来了一个额外的好处,就是当收到一个优先级高的请求时,比如接收到 JavaScript 或者 CSS 关键资源的请求,服务器可以暂停之前的请求来优先处理关键资源的请求。

聊一聊 HTTP 发展史

现在我们知道为了解决 HTTP/1.1 存在的问题,HTTP/2 采用了多路复用机制,那 HTTP/2 是怎么实现多路复用的呢? 你可以先看下面这张图:

聊一聊 HTTP 发展史 HTTP/2 协议栈从图中可以看出,HTTP/2 添加了一个二进制分帧层,那我们就结合图来分析下 HTTP/2 的请求和接收过程。

  • 首先,浏览器准备好请求数据,包括了请求行、请求头等信息,如果是 POST 方法,那么还要有请求体。
  • 这些数据经过二进制分帧层处理之后,会被二进制格式转换为一个个带有请求 ID 编号的帧,通过协议栈将这些帧发送给服务器。
  • 服务器接收到所有帧之后,会将所有相同 ID 的帧合并为一条完整的请求信息。然后服务器处理该条请求,并将处理的响应行、响应头和响应体分别发送至二进制分帧层。
  • 同样,二进制分帧层会将这些响应数据转换为一个个带有请求 ID 编号的帧,经过协议栈发送给浏览器。浏览器接收到响应帧之后,会根据 ID 编号将帧的数据提交给对应的请求。

从上面的流程可以看出,通过引入二进制分帧层,就实现了 HTTP 的多路复用技术,解决了宽带占用率低的问题。还有下面几个特性:

  • 可以设置请求的优先级
  • HTTP/2 还可以直接将数据提前推送到浏览器
    • 当用户请求一个 HTML 页面之后,服务器知道该 HTML 页面会引用几个重要的 JavaScript 文件和 CSS 文件,那么在接收到 HTML 请求之后,附带将要使用的 CSS 文件和 JavaScript 文件一并发送给浏览器,这样当浏览器解析完 HTML 文件之后,就能直接拿到需要的 CSS 文件和 JavaScript 文件,这对首次打开页面的速度起到了至关重要的作用。
  • 头部压缩

HTTP/2 解决了 HTTP/1.1 中的队头阻塞问题,但是 HTTP/2 依然是基于 TCP 协议的,而 TCP 协议依然存在数据包级别的队头阻塞问题,那么 TCP 的队头阻塞是如何影响到 HTTP/2 性能的呢?除了 TCP 队头阻塞之外,TCP 的握手过程、TLS 也需要一个握手过程的花销也是影响传输效率的一个重要因素。

先讲一下他们的区别:

  1. HTTP/1.1的头阻塞:在HTTP/1.1中,存在着一种现象称为头阻塞(Head-of-Line Blocking)。这是由于HTTP/1.1使用了串行的请求-响应模型,在一个TCP连接上通过顺序发送多个请求。当其中一个请求遇到阻塞(如网络延迟或服务器处理时间长)时,后续请求将被阻塞,无法继续进行。换句话说,后续请求需要等待前面的请求完成才能发送和处理,导致整体性能下降。
  2. TCP数据包级别的头阻塞:TCP是一种面向连接的可靠传输协议,用于在应用层之间传输数据。TCP协议将应用层的数据划分为多个数据包,并通过网络传输。在TCP数据包级别上的头阻塞是指在一个TCP连接中,如果其中一个数据包丢失、损坏或出现延迟,那么后续的数据包将被阻塞,无法继续传输,直到之前的数据包被重新发送或接收完成。

总结起来,HTTP/1.1的头阻塞是在应用层面上,由于串行请求-响应模型的特点导致后续请求受阻。而TCP数据包级别的头阻塞是在传输层面上,由于TCP协议的可靠性和有序性要求导致后续数据包受阻。两者是不同层次的问题,但都会对网络性能和响应时间产生负面影响。为了解决这些问题,HTTP/2引入了多路复用和二进制分帧等技术,以减轻头阻塞的影响,提高并发性和性能。

TCP 的握手过程的花销,TLS 也需要一个握手过程:

网络延迟又称为 RTT(Round Trip Time),在建立 TCP 连接的时候,需要和服务器进行三次握手来确认连接成功,也就是说需要在消耗完 1.5 个 RTT 之后才能进行数据传输。进行 TLS 连接,TLS 有两个版本——TLS1.2 和 TLS1.3,每个版本建立连接所花的时间不同,大致是需要 1~2 个 RTT,总之,在传输数据之前,我们需要花掉 3~4 个 RTT。

现在我们知道了 TCP 协议存在队头阻塞和建立连接延迟等缺点,那我们是不是可以通过改进 TCP 协议来解决这些问题呢?

答案是:非常困难。

之所以这样,主要有两个原因。第一个是中间设备的僵化操作系统也是导致 TCP 协议僵化的另外一个原因。因为 TCP 协议都是通过操作系统内核来实现的,应用程序只能使用不能修改。通常操作系统的更新都滞后于软件的更新,因此要想自由地更新内核中的 TCP 协议也是非常困难的。

http/3.0

QUIC 协议的构思诞生:

  • HTTP/3 选择了一个折衷的方法——UDP 协议,基于 UDP 实现了类似于 TCP 的多路数据流、传输可靠性等功能,我们把这套功能称为 QUIC 协议。关于 HTTP/2 和 HTTP/3 协议栈的比较,你可以参考下图: 聊一聊 HTTP 发展史

通过上图我们可以看出,HTTP/3 中的 QUIC 协议集合了以下几点功能。

  • 实现了类似 TCP 的流量控制、传输可靠性的功能。虽然 UDP 不提供可靠性的传输,但 QUIC 在 UDP 的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些 TCP 中存在的特性。

  • 集成了 TLS 加密功能。目前 QUIC 使用的是 TLS1.3,相较于早期版本 TLS1.3 有更多的优点,其中最重要的一点是减少了握手所花费的 RTT 个数。

  • 实现了 HTTP/2 中的多路复用功能。和 TCP 不同,QUIC 实现了在同一物理连接上可以有多个独立的逻辑数据流(如下图)。实现了数据流的单独传输,就解决了 TCP 中队头阻塞的问题。

    QUIC 协议的多路复用 聊一聊 HTTP 发展史

  • 实现了快速握手功能。由于 QUIC 是基于 UDP 的,所以 QUIC 可以实现使用 0-RTT 或者 1-RTT 来建立连接,这意味着 QUIC 可以用最快的速度来发送和接收数据,这样可以大大提升首次打开页面的速度。

HTTP/3 的挑战

第一,从目前的情况来看,服务器和浏览器端都没有对 HTTP/3 提供比较完整的支持。Chrome 虽然在数年前就开始支持 Google 版本的 QUIC,但是这个版本的 QUIC 和官方的 QUIC 存在着非常大的差异。

第二,部署 HTTP/3 也存在着非常大的问题。因为系统内核对 UDP 的优化远远没有达到 TCP 的优化程度,这也是阻碍 QUIC 的一个重要原因。

第三,中间设备僵化的问题。这些设备对 UDP 的优化程度远远低于 TCP,据统计使用 QUIC 协议时,大约有 3%~7% 的丢包率。