花10分钟理解 HTTP/1,2,3 这5条特性,基础就牢了

大家好呀,我是码财同行。

HTTP 协议大家也是再熟悉不过了,尤其是 web 开发的前后端同学。HTTP 协议的发展也经历了很长时间。今天,我们就来聊聊它。

HTTP

大家可能知道,20世纪90年代,互联网在美国诞生,随之得到了迅猛发展,美股90年代的大牛市正是互联网的发展带来的。

HTTP 协议随之诞生。它是一种非常简单的协议,它基于 TCP,在应用协议层做了以下规定:

  • HTTP 是一个文本协议,使用和调试起来很方便
  • 约定了具体的格式,如包头(方法、URI等)、空行、包体等
  • 采用 request-response 的交互模式

1996年,HTTP/1.0 正式定型,具体到它的交互模式,是这样的:

rust 复制代码
建立连接(tcp三次握手)  --> 发送 request 1,等待 response 1 --> 关闭连接(tcp四次挥手)
建立连接(tcp三次握手)  --> 发送 request 2,等待 response 2 --> 关闭连接(tcp四次挥手)

这就是 HTTP/1.0 的短连接模式。大家可能发现了,这样的协议规定好像有点点怪:干嘛要关闭连接之后又继续建立连接?直接继续发送请求、接收响应呗。

于是很快,官方组织很快接受了大家的意见,1997年 HTTP/1.1 发布,解决了这个问题。

HTTP/1.1协议,连接建立后,不会关闭,可以一直发送数据和接收数据,这就是长连接模式。长连接的交互方式变成:

  • 建立连接(tcp三次握手)
  • 发送 request 1,等待 response 1
  • 发送 request 2,等待 response 2
  • 发送 request 3,等待 response 3
  • ...
  • 关闭连接(tcp四次挥手)

这样提高了效率,去掉了多次建立连接关闭连接的开销。

http请求头里 Connection:keep-alive 可以开启长连接模式,不过一般默认就是开启 keepalive 长连接模式。

二、HTTP/2

串行问题

HTTP/1.1 虽然解决了连接频繁建立的问题,但是还存在一个问题,就是串行(专业术语叫队头阻塞)。因为 HTTP 协议规定的就是 request-response 模式:request 2 必须要等到 response 1 收到之后才能发送出去。

从 HTTP 客户端的角度,简单用一张图来表示:

这种串行处理的方式为什么问题很大?

首先是慢,http 的客户端需要一个个请求的等待,如果遇到下载资源等比较耗时的请求,体验会非常差。

其次,服务器在处理一个请求的时候,很有可能有富余的资源来处理其他的请求,因为服务器往往有强大的高并发能力,能并行处理多个请求。如果是串行通信,必须等前一个请求处理完再发起另外一个请求,是对服务器资源的很大浪费。

那么怎么解决所有请求串行传输的问题呢?

答案就是并发传输

并发传输

并发这种思想在 CPU 支持多任务操作系统的切换时候就有应用,就是所谓的时分复用:CPU 的时间被分成一个个时间片,每个程序的执行流也被切分成一小份一小份,CPU 一会执行这个程序的执行流,一会执行那个程序的执行流;而从整体上看,所有程序就是并行执行(交替执行)的。

有了 HTTP/2,可以一次性并发发起多个 request,这些 request 被拆分成无差别的 stream,通过 HTTP 的连接通道(TCP连接)传输。

多个 request 以 stream 为单位被并发的发给服务器,服务器收到 stream 后,再还原出原先的多个 request,就可以并行处理了。类似的,response 的传输也是并发进行的。

整个过程如下图所示:

一条 HTTP 连接(底层 tcp, 发送和接收两个方向)的通道被切分成多个 stream 来传输数据,每个 HTTP request 占用几个,从整体上看,就是多个 request 被同时发送。

这种在一条连接上并发传输的技术更学院派的叫法是 多路复用(multiplexing)

很显然,HTTP/2 为什么引入 stream 的技术也有了答案,类似于 CPU 的时间被切分成多个时间片,长的协议包数据也被切分成多个 stream,方便交替的传输。

而正是由于包被并发的传输到服务器,服务器也能并发处理客户端的多个 request。

推送

HTTP/2 除了引入并发传输/多路复用的技术,还支持服务器主动推送。

由于 HTTP/2 没有了 request - response 的限制,包就不需要一一对应,服务器可以主动推送协议包给客户端,这种单一的包传输方便了很多资源的下发,如下图中 css 文件的下发:

在 Golang 中,服务器实现这种推送的代码也很简单:

golang 复制代码
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    // 正常处理 request 并发送 response
    //...
    
    // 服务主动单向推送新的协议包
    if pusher, ok := w.(http.Pusher); ok {
        if err := pusher.Push("/static/example.css", nil); err != nil {
            log.Printf("Failed to push: %v", err) 
        } 
    }    
    // ... 
 })

三、HTTP/3

2018年11月,HTTP/3 得到了官方批准。

基于 TCP 的问题

HTTP/2 2015年才发布,为何 HTTP/3 这么快又来了?!

我们知道了,HTTP/1.1、HTTP/2.0 在传输层上都是基于 TCP 的,而 TCP 有自己的问题

这时候,小伙伴们就要问了:TCP 有哪些问题呢?

我们列举一下:

  • 强调公平,效率不高。只要网络一有风吹草动,如网络状况变差,立马谦虚礼让,之前1秒传输1k,现在给你缩减到100 ~
  • 需要三次握手建立连接,效率不高。即使连接建立好之后,TCP 和咱们打工人一样,也不是假期归来一秒进入工作状态,还是有不应期的,而是要一点点慢慢来,传输的数据量根据一定规则提升,直到跑满。这就是所谓的 慢启动
  • 把传输视为 字节流,有严格的先后顺序,而实际上很多web应用需要并行传输很多数据,充分利用带宽。比如,可能访问一个网站,会同时下载很多资源。此时就有严重的串行/队头阻塞问题。

这里,小伙伴肯定有疑惑了,前面不是说 HTTP/2 已经可以并发传输协议了么?

其实,HTTP/2 并没有完全解决串行/对头阻塞这个问题。客户端在并发的收到服务器回的 stream 时,仍然还需要组装成多个 response,这些 response 还是串行的返回给调用层,严格按照顺序。

HTTP/2 最大的改进,其实是充分利用了服务器的并发处理能力。虽然协议包能并发传输,但是并发传输并不代表一定是并行传输,因为底层还是唯一的一条 TCP 连接。

熟悉并发/并行术语的同学就能理解了,HTTP/2 做到了并发,但底层执行的时候没有做到并行,还是在 TCP 的串行通道上传输。这就无法充分利用带宽,满负荷工作。

还因为前面提到的 TCP 本身的问题,单独一条 TCP 连接本身的传输效率也并不高。

为此,HTTP/3 决定放弃底层的 TCP,改用 QUIC 协议

QUIC 对 TCP 的替换

QUIC 是基于 UDP 的。

在 UDP 协议中,并没有连接的概念(其实 TCP 也没有实体的连接概念,只是规则上类似一条逻辑上的连接),没有 TCP 中字节流顺序传输的约定。

传输的时候,以报文为单位,可以随便发,传输的时候可以并行传输在网络上。完全没有规则。

QUIC 协议为什么要基于 UDP 呢?为何不直接新增一个传输层协议,和 TCP、UDP并列?

原因还是这种操作系统协议栈级别的更新会涉及成千上万种设备,替换是几乎不可能的。

无论是改进 TCP 为 TCP2.0 或者新发明一种协议,都需要旷日持久的时间。

而 UDP 协议已经广泛采用,且简单,没有特殊的功能。基于 UDP 是最优选择。

但是 QUIC 也不能等于 UDP,因为 UDP 毫无规则,丢包了也不知道,网络阻塞了也不知道。因此,QUIC 还是需要一种类似 TCP 一样保证可靠性,有流量控制及拥塞控制这些基本的规则,只不过,这些规则实现起来比 TCP 更高效。

从这个意义上说,QUIC 是一种可靠的UDP协议。

具体来说,基于 QUIC 的 HTTP/3 模型示意图如下:

QUIC 设计了不同于 HTTP/2 的 stream 机制),可以以 stream 为单位发起重传,互相不影响。而在 HTTP/2 中,如果有 stream 丢包,从根本上解决了队头阻塞问题。

举个例子,现在要传输3个文件,假如每个文件都可以被切分成4个stream,HTTP/1.1 和 HTTP/2 中分别是这样传输:

HTTP/1.1 中 B 必须等 A全部传输完毕,C也类似;HTTP/2 中可以交替传输。

此时,如果在 HTTP/2 中第三个 stream,也就是文件 B 发生了丢包:

此时,虽然后面的 stream 都正常传输过去,但是 tcp 并不知道,TCP 是基于字节流的,它的概念中没有什么 A、B、C 这种 stream,因此它只能重新发送整段未被确认的字节流!

因此,即使后面的字节流已经收到了,也只能等前面一段字节流传输完毕,才能继续。

从这个角度来说,基于 TCP 的 HTTP/2 虽然在上层实现了 stream,但底层还是字节流,尤其是重传的时候,不能按照小块的 stream 来重传,效率还是较低。

而 HTTP/3 就没有这个问题,它的传输和重传都是基于 stream 的,前后不用等待,多个协议包之间完全互不影响。

四、HTTP 协议的普及情况

根据 w3techs.com 的统计,HTTP/2、HTTP/3 的普及基本达到了 30% 左右:

因此,HTTP/2 和 HTTP/3 的普及速度还是不错的。如果情况允许,还是尽量升级到新的版本吧。

小结

到这里,HTTP 几个版本的特性我们就了解完了,总结一下:

  • HTTP/1 实现了基本的网络协议,有串行传输的问题;
  • HTTP/2 采用了并发/多路复用的技术,最大程度上利用了服务器的处理能力;
  • HTTP/3 摒弃了前两个版本底层的TCP,实现了可靠的基于UDP的机制,从根本上解决了传输效率以及串行/队头阻塞问题

好了,看了这么多,一定很费脑力吧。来个笑话放松一下 :)

【笑话一则】婚前偶尔去媳妇家住,通常我住她房间,她住客厅。每次想啪啪啪,得等她家人都睡了,然后吹三长两短口哨。一晚我又吹了暗号,媳妇久久没反应。我就纳闷,探头往客厅看,见老丈人正在推醒熟睡的媳妇:喊你进去呢!

感谢您花时间阅读这篇文章!如果觉得有趣或有收获,请来个关注、评论、点赞吧,您的鼓励是我持续创作的动力,蟹蟹!

| 往期推荐

# 协程没有秘密(二):几张图文快速入门协程,推荐收藏

基于本地知识库,定制一个私有GPT助手,不能再简单了

【建议收藏】服务注册与发现原理+踩坑,一文包教会

【技术·真相】谈一谈游戏AI - 真的搞懂寻路(一)

【技术·真相】谈一谈K8S的存储(一)

相关推荐
javachen__5 分钟前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql
阿珊和她的猫2 小时前
v-scale-scree: 根据屏幕尺寸缩放内容
开发语言·前端·javascript
uzong6 小时前
技术故障复盘模版
后端
GetcharZp6 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
加班是不可能的,除非双倍日工资6 小时前
css预编译器实现星空背景图
前端·css·vue3
桦说编程6 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研6 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi7 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip7 小时前
vite和webpack打包结构控制
前端·javascript
玩转以太网7 小时前
基于W55MH32Q-EVB 实现 HTTP 服务器配置 OLED 滚动显示信息
服务器·网络协议·http