Computer Network

Http各版本的区别与升级

HTTP 1.0

NetworkHTTP&HTTPS

HTTP 1.0

  1. 请求默认发起短连接,每次请求都需要建立连接。(但支持长连接)
  2. 不支持Host头,一个IP只能绑定一个网站
  3. 引入了 Content-Type ,使得传输的数据不再限于文本。
  4. 引入了请求头和响应头,使得请求和响应更加的灵活
  5. 请求严格串行化,未收到上一个请求的响应前不能发送下一个请求。
  6. 只能靠Content-Length来表示消息结束
  7. 缓存能力一般,只有Expires强制缓存和Last-Modified协商缓存

HTTP/1.0的主要问题:

  1. 默认短连接,连接无法复用,每次请求都需要创建新链接

  2. 无Host头,一个IP只允许绑定一个网站。(如果一个服务器部署多了网站,那么在网站域名解析时都会指向这个服务器IP,那服务器其实不知道客户端要的是那个网站的数据)

  3. 请求串行化,前一个请求没响应就不能发下一个请求,有对头阻塞

  4. 服务端响应只能依赖Content-Length,但对于服务端不知道数据最终有多大,长度都是是动态生成的,是边算边输出的场景就有问题。而HTTP/1.0要求必须提前算出总大小,因此只能算完再一起发,延迟就很高。

  5. 缓存能力一般

    • 强制缓存:第一次请求后服务器返回Expires: Wed, 21 Oct 2026 07:28:00 GMT,表示在这个时间之前缓存都能使用。他的问题在于:依赖于客户端的时间,如果客户端时间不准就会出问题

    • 协商缓存:第一次请求后服务器返回Last-Modified: Wed, 21 Oct 2026 07:00:00 GMT。浏览器保存这个时间,第二次请求时浏览器带上If-Modified-Since: Wed, 21 Oct 2026 07:00:00 GMT,意思是“这个资源自从这个时间之后有没有修改?”服务器会检查:

      • 没改 → 返回 304(不带内容),浏览器使用缓存
      • 改了 → 返回 200 + 新内容

      他的问题在于:精度只有秒级,如果 1 秒内修改两次,检测不到。并且判断标准只依赖时间,如果文件内容没变,但时间变了,也会重新下载。

HTTP/1.1

  1. 默认长连接,一次TCP连接可以发送多个请求
  2. 支持pipline,无序等待前面请求的响应就能发送下一次请求。(但客户端响应时只有响应了前一个请求才能响应下一个请求)。因此依然有对头阻塞的问题
  3. 新增Host头,一个 IP 可以部署多个网站(在请求中带上Host头,Host头通常就是要请求数据的网站域名)
  4. 允许服务端响应数据分块(chunked),即响应的时候不标明Content-Length,客户端就无法断开连接,直到收到服务端的 EOF ,利于传输大文件。
  5. 升级了缓存能力,引入了Cache-Control,ETag。
    • Cache-Control直接返回过期时间,这样就不用依赖客户端时间,更精确
    • 引入ETag实现更精准的协商缓存,服务端会返回ETag: "abc123"作为资源的指纹,客户端下次请求带上If-None-Match: "abc123",服务器会判断资源内容的哈希值是否一样,一样则返回304,浏览器使用缓存;不一样则返回200 + 新数据。不依赖时间作为判断条件,更精准

HTTP/1.1的问题在于:

  1. 虽然有pipline,但在响应时依然是对头阻塞
  2. 虽然可以用Content-Encoding指定Body的压缩方式,但是Header依然无法压缩,而Header又通常会发送大量重复的数笔,比如Cookie,User Agent等

HTTP/2

  1. 前面的HTTP/1.x都是文本协议,HTTP/2升级为二进制协议。所有数据拆成帧
  2. 支持多路复用,一次TCP连接可以发起多个请求,并且响应可以交错,不再像pipline那样需要串行
  3. Header 压缩(HPACK),由于各种各样功能的增加,HTTP的请求头其实很大,来回传这些重复信息很消耗带宽。HTTP/2使用静态表,动态表,索引复用等大幅减少头部体积。
  4. 服务推动,服务端可以“预判”你的需求。比如你请求了 index.html,服务端知道你肯定还要 style.css,就主动把它推给你,不用你再发请求来要。(不过由于不好控制,并且会和缓存冲突,就废弃了)

HTTP/2.0的问题在于无法解决TCP层面的对头阻塞,且由于TCP和TLS的双重握手,导致最快也至少需要2-3个RTT才能建立连接

HTTP/3

虽然HTTP/2引入了多路复用,解决了HTTP对头阻塞的问题,但是由于HTTP本质基于TCP,而TCP为了保证消息有序,不丢失等。如果丢一个包后续抵达的包也不会马上接受,需要等到丢失的包重传到后才能响应,这就是TCP层面的对头阻塞。

并且TCP是通过四元组(源IP,源端口,目标IP,目标端口)来唯一确定连接的,在现代的移动网络环境下,IP随机都可能发生变化(比如一会又是流量,一会又是不同地方的WIFI),导致反复建立连接

但由于TCP已经出来很久了,换句话说就是历史包袱沉重,直接动刀TCP不太现实。

因此Google把目光抛向了UDP,UDP面向无连接,没有重传,不保证顺序。很激进,速度也很快。Google的QUIC就是基于UDP的,而像是有序,重传,流量控制等都提到应用层来实现。

QUIC通过Connection ID 来标识一个链接,所以切换网络之后可以复用这个连接,达到 0 RTT 就能开始传输。

Q&A

HPACK 和 QPACK 都提到了静态表和动态表,能说说它们的区别和作用吗?

首先静态表是预定义好的,其实就是一些常见的Header,比如 :method: GET、content-type: text/html 这些。客户端和服务端都内置了,直接用索引号代替,省得每次发全文

动态表是在运行时建立的,遇到新 Header 就往里加,下次再遇到同样的 Header 直接发索引。

HPACK 的动态表要求严格有序,必须等前面的都到了才能更新;QPACK 改进了这点,动态表的更新走单独的单向流,解码端收到更新再用,避免了因为乱序到达导致的阻塞。

HTTP/2 的服务端推送为什么在 HTTP/3 被废弃了?实际用的时候有什么问题?

主要是实际效果没想象的好,还带来一堆麻烦。

因为服务端很难精准判断客户端到底需要什么,还容易和缓存的功能相冲突,有可能推过去的资源可能客户端缓存里已经有了,白白浪费带宽。

还有就是控制粒度太粗,服务端推了一堆东西,客户端想取消也麻烦。加上 CDN 这类中间层处理起来也复杂,最后大家发现还不如让客户端自己发请求来得靠谱。

post.comments