【从0开始学计算机网络】| HTTP 协议的进化史
【从0开始学计算机网络】| HTTP 协议的进化史前言调 Nginx 时见过 keep-alive看浏览器 DevTools 时见过 h2面试里被问过HTTP/2 为什么快——这些零散的片段其实都指向同一条主线HTTP 这三十多年是怎么一步步进化过来的。这篇文章把 1.0 到 3.0 的进化逻辑串起来重点讲清楚每个版本当时卡在哪儿、怎么解决、又留下了什么新问题。一、使用场景这个知识点会在三类场景里反复出现。排查页面加载慢。一个页面几十个请求到底慢在建连接、慢在排队、还是慢在传输得先搞清楚当前走的是哪个协议版本、队头阻塞发生在哪一层。配置服务器和网关。Nginx 的keepalive_timeout、upstream 连接池、Spring Boot 的 HTTP/2 开关这些配置的含义全部挂在版本演进上不了解演进史就只能照抄配置。准备面试。HTTP 版本区别是计算机网络的高频考题只背特性表很难答出层次理解进化逻辑才能把为什么讲清楚。二、核心概念HTTP 本质上是一份通信约定浏览器把我要这个资源翻译成一段固定格式的报文发出去服务器按同样的格式把内容和状态传回来。GET /index.html HTTP/1.1 Host: www.example.com这就是一次最小的 HTTP 请求。四个版本各自解决了一个瓶颈先看总览版本核心变化解决了什么HTTP/0.9只有 GET 方法只能传 HTML 文档HTTP/1.0引入状态码和头字段支持多类型资源内容从文字扩展到图片、音视频HTTP/1.1长连接、Host头、分块传输、缓存控制反复重建连接的开销HTTP/2.0二进制分帧、多路复用、HPACK 压缩同一连接内串行排队HTTP/3.0底层换成 QUIC跑在 UDP 上TCP 队头阻塞、握手慢、连接不能迁移看这张表的方式不是背而是顺着瓶颈→解法往下读1.0 卡在连接1.1 解决了连接又带来排队2.0 解决了排队又撞上 TCP 本身3.0 干脆把传输层一起换掉。三、最小代码不用写任何后端代码几条命令就能把版本差异摸一遍。用nc手写一个 HTTP/1.0 请求printfGET / HTTP/1.0\r\nHost: example.com\r\n\r\n|ncexample.com80响应读完连接立刻关闭。同样的请求换成 1.1printfGET / HTTP/1.1\r\nHost: example.com\r\n\r\n|ncexample.com80响应结束后连接会保持一段时间还能在这条连接上继续发第二个请求——这就是长连接最直观的形态。再用curl看协议协商结果curl-sv--http1.1-o/dev/null https://example.org21|grep-ihttpcurl-sv--http2-o/dev/null https://example.org21|grep-ihttp后一条的输出里会出现 Using HTTP2 的字样说明服务端支持且协商成功。想在自己项目里打开 HTTP/2Spring Boot 加一个配置即可配置键随版本略有差异以所用版本的官方文档为准server:http2:enabled:true四、执行流程同一次页面加载在四个版本下走的是完全不同的流程。**HTTP/1.0一个请求一条连接。**页面里要用到 index.html、main.js、style.css 和 logo.png 四个资源浏览器就得做四轮三次握手、发请求、收响应、四次挥手时间大头花在建连接而不是传数据上。1.0 真正的历史意义是把可传输的内容从纯文字扩展到了图片、音视频、CSS 和 JS。**HTTP/1.1连接复用但请求串行。**一条 TCP 连接建立后反复用于多轮请求和响应建连接的开销被摊薄握手次数大幅下降Host头让一台服务器能靠域名区分多个站点虚拟主机因此跑了起来。但同一条连接里响应必须按请求顺序返回第一个请求是一张几 MB 的大图后面的小请求就算服务器早就处理完也得排队等——这就是队头阻塞。**HTTP/2.0二进制分帧加多路复用。**报文拆成二进制帧同一连接上的多路数据交错传输不存在排队。两个配套优化HPACK 让 Cookie 和 User-Agent 这类每次重复的字段只传增量Server Push 让服务器在浏览器请求 HTML 时顺手把关联资源推过去。要注意HTTP/2 消灭的只是 HTTP 层的排队传输层的排队原封不动——TCP 坚持按序交付中间缺了一个包后面已到达的数据全部积压整条连接上的流都得陪着等。**HTTP/3.0把传输层从 TCP 换成 QUIC。**协议栈从 HTTP → TCP → IP 变成 HTTP → QUIC → UDP → IP。QUIC 在用户态实现可靠传输每条流独立做丢包恢复一条流丢包不再拖累别的流握手与 TLS 合并首次连接 1-RTT重连可 0-RTT连接靠连接 ID 标识而不是 IP 加端口手机从 WiFi 切到移动网络连接不断这对移动端体验是质的变化。五、常见坑**把 HTTP/2 当万能提速。**高丢包的弱网环境下它可能反而更慢1.1 时代浏览器对同一域名开 6 条连接丢包只卡住一条HTTP/2 把所有请求挤进一条 TCP 连接丢一次包全部卡住。**协议版本没生效却不自知。**Nginx 监听参数里少了 http2或 TLS 握手没协商出 ALPN浏览器会静默降级回 1.1页面照样能打开只是快不起来。这种问题只能靠 DevTools 的Protocol列或curl抓出来。**keep-alive 两端超时不一致。**Nginx 到后端开着 keepalive 连接池Tomcat 的 keep-alive 超时却比 Nginx 短Nginx 拿一条已被后端关掉的连接发请求就会出现偶发 502 或 connection reset。两头超时要配成上游略长于下游。**把 Server Push 当长期依赖。**它的实际收益长期低于预期Chrome 从 106 版本起已移除支持新设计用 preload 或 103 Early Hints 代替。**默认信任 0-RTT 的重放安全性。**QUIC 重连时的 0-RTT 数据会在服务器确认身份之前就被处理攻击者可以原样重放这段数据来重复触发操作。下单、支付这类非幂等请求不能放进 0-RTT需要在应用层单独拦截。六、验证方式每条演进结论都能在本机验证。验证长连接curl -v连续请求同一站点两次输出里第二次会出现 Re-using existing connection加 --http1.0 再试两次都会新建连接。验证当前页面的协议版本DevTools 的 Network 面板表头右键勾选Protocol列能看到每个请求走的是 h2 还是 http/1.1。验证队头阻塞1.1 下对同一域名并发请求一个大文件和几个小文件观察小文件的 Queueing 和 Stalled 时间同样的请求切到 HTTP/2小文件不再排在大文件后面。连接迁移的完整验证需要 QUIC 环境成本较高可以退而求其次读 RFC 9000 里连接 ID 的章节或用 wireshark 抓 QUIC 包看 Initial 包里的连接 ID 字段。七、总结一个可迁移的方法看协议演进不要背特性表先问当时卡在哪个瓶颈。1.0 卡在连接1.1 卡在排队2.0 卡在 TCP3.0 把传输层一起换掉。带着这个框架去看 Nginx、Tomcat、Netty 的连接管理配置或者去答面试题都会顺很多。