深度了解HTTP/2

  • A+
所属分类:Linux

自从我写了上一篇博文之后,就再也找不到空闲时间写文章了。今天我终于可以抽出时间写一些关于 HTTP 的东西。

我认为每一个 web 开发者都应该对这个支撑了整个 Web 世界的 HTTP 协议有所了解,这样才能帮助你更好的完成开发任务。

在这篇文章中,我将讨论什么是 HTTP,它是怎么产生的,它的地位,以及我们应该怎么使用它。

HTTP 是什么

首先我们要明白 HTTP 是什么。HTTP 是一个基于 TCP/IP 的应用层通信协议,它是客户端和服务端在互联网互相通讯的标准。它定义了内容是如何通过互联网进行请求和传输的。HTTP 是在应用层中抽象出的一个标准,使得主机(客户端和服务端)之间的通信得以通过 TCP/IP 来进行请求和响应。TCP 默认使用的端口是 80,当然也可以使用其它端口,比如 HTTPS 使用的就是 443 端口。

HTTP/0.9 - 单行协议 (1991)

HTTP 最早的规范可以追溯到 1991 年,那时候的版本是 HTTP/0.9,该版本极其简单,只有一个叫做 GET的请求方式。如果客户端要访问服务端上的一个页面,只需要如下非常简单的请求:

服务端对应的返回类似如下:

就这么简单,服务端捕获到请求后立马返回 HTML 并且关闭连接,在这之中

  • 没有头信息headers
  • 仅支持 GET 这一种请求方法
  • 必须返回 HTML

如同你所看到的,当时的 HTTP 协议只是一块基础的垫脚石。

HTTP/1.0 - 1996

在 1996 年,新版本的 HTTP 对比之前的版本有了极大的改进,同时也被命名为 HTTP/1.0

HTTP/0.9 只能返回 HTML 不同的是,HTTP/1.0 支持处理多种返回的格式,比如图片、视频、文本或者其他格式的文件。它还增加了更多的请求方法(如 POSTHEAD),请求和响应的格式也相应做了改变,两者都增加了头信息;引入了状态码来定义返回的特征;引入了字符集支持;支持多段类型multi-part、用户验证信息、缓存、内容编码格式等等。

一个简单的 HTTP/1.0 请求大概是这样的:

正如你所看到的,在请求中附带了客户端中的一些个人信息、响应类型要求等内容。这些是在 HTTP/0.9 无法实现的,因为那时候没有头信息。

一个对上述请求的响应例子如下所示:

HTTP/1.0 (HTTP 后面跟的是版本号)早期开始,在状态码 200 之后就附带一个原因短语(你可以用来描述状态码)。

在这个较新一点的版本中,请求和响应的头信息仍然必须是 ASCII 编码,但是响应的内容可以是任意类型,如图片、视频、HTML、文本或其他类型,服务器可以返回任意内容给客户端。所以这之后,HTTP 中的“超文本Hyper Text”成了名不副实。 HMTP超媒体传输协议Hypermedia transfer protocol)可能会更有意义,但是我猜我们还是会一直沿用这个名字。

HTTP/1.0 的一个主要缺点就是它不能在一个连接内拥有多个请求。这意味着,当客户端需要从服务器获取东西时,必须建立一个新的 TCP 连接,并且处理完单个请求后连接即被关闭。需要下一个东西时,你必须重新建立一个新的连接。这样的坏处在哪呢?假设你要访问一个有 10 张图片,5样式表stylesheet5 个 JavaScript 的总计 20 个文件才能完整展示的一个页面。由于一个连接在处理完成一次请求后即被关闭,所以将有 20 个单独的连接,每一个文件都将通过各自对应的连接单独处理。当连接数量变得庞大的时候就会面临严重的性能问题,因为 TCP 启动需要经过三次握手,才能缓慢开始。

三次握手

三次握手是一个简单的模型,所有的 TCP 连接在传输应用数据之前都需要在三次握手中传输一系列数据包。

  • SYN - 客户端选取一个随机数,我们称为 x,然后发送给服务器。
  • SYN ACK - 服务器响应对应请求的 ACK 包中,包含了一个由服务器随机产生的数字,我们称为 y,并且把客户端发送的 x+1,一并返回给客户端。
  • ACK - 客户端在从服务器接受到 y 之后把 y 加上 1 作为一个 ACK 包返回给服务器。

一旦三次握手完成后,客户端和服务器之间就可以开始交换数据。值得注意的是,当客户端发出最后一个 ACK 数据包后,就可以立刻向服务器发送应用数据包,而服务器则需要等到收到这个 ACK 数据包后才能接受应用数据包。

http

请注意,上图有点小问题,客户端发回的最后一个 ACK 包仅包含 y+1,上图应该是 ACK:y+1 而不是 ACK:x+1,y+1

然而,某些 HTTP/1.0 的实现试图通过新引入一个称为 Connection: keep-alive 的头信息来克服这一问题,这个头信息意味着告诉服务器“嘿,服务器,请不要关闭此连接,我还要用它”。但是,这并没有得到广泛的支持,问题依然存在。

除了无连接之外,HTTP 还是一个无状态的协议,即服务器不维护有关客户端的信息。因此每个请求必须给服务器必要的信息才能完成请求,每个请求都与之前的旧的请求无关。所以,这增 加了推波助澜的作用,客户端除了需要新建大量连接之外,在每次连接中还需要发送许多重复的数据,这导致了带宽的大量浪费。

HTTP/1.1 - 1999

HTTP/1.0 经过仅仅 3 年,下一个版本,即 HTTP/1.1 就在 1999 年发布了,改进了它的前身很多问题,主要的改进包括:

  • 增加了许多 HTTP 请求方法,包括 PUTPATCHHEADOPTIONSDELETE
  • 主机标识符 HostHTTP/1.0 并不是必须的,而在 HTTP/1.1 是必须的。
  • 如上所述的持久连接。在 HTTP/1.0 中每个连接只有一个请求并在该请求结束后被立即关闭,这导致了性能问题和增加了延迟。 HTTP/1.1 引入了持久连接,即连接在默认情况下是不关闭并保持开放的,这允许多个连续的请求使用这个连接。要关闭该连接只需要在头信息加入 Connection: close,客户通常在最后一个请求里发送这个头信息就能安全地关闭连接。
  • 新版本还引入了“管线化pipelining”的支持,客户端可以不用等待服务器返回响应,就能在同一个连接内发送多个请求给服务器,而服务器必须以接收到的请求相同的序列发送响应。但是你可能会问了,客户端如何知道哪里是第一个响应下载完成而下一个响应内容开始的地方呢?要解决这个问题,头信息必须有 Content-Length,客户可以使用它来确定哪些响应结束之后可以开始等待下一个响应。
    • 值得注意的是,为了从持久连接或管线化中受益, 头部信息必须包含 Content-Length,因为这会使客户端知道什么时候完成了传输,然后它可以发送下一个请求(持久连接中,以正常的依次顺序发送请求)或开始等待下一个响应(启用管线化时)。
    • 但是,使用这种方