编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 计算机原理

  • 网络协议

    • README
    • 通过看新闻熟悉网络
    • 通过购物熟悉加密
    • 从0到1部书电商网站
    • 请求网络的通用流程
    • 网络编程模型的概念
    • 传输协议TCP和UDP
    • Socket的发展和设计
    • 传输数据的设计思想
    • 网络域名解析的流程
    • HTTP服务设计流程
    • HTTP协议设计思想
    • HTTPS协议设计策略
    • HTTP连接和跳转
      • 01.工作案例引入
        • 1.1 一个资讯App的连接优化之路
        • 1.2 问题背后的HTTP连接知识图谱
      • 02.HTTP连接管理
        • 2.1 短连接的问题
        • 2.2 长连接(Keep-Alive)
        • 2.3 连接池设计
        • 2.4 HTTP/2多路复用
        • 2.5 HTTP管线化
      • 03.HTTP重定向机制
        • 3.1 什么是重定向
        • 3.2 重定向状态码
        • 3.3 永久重定向vs临时重定向
        • 3.4 重定向的应用场景
        • 3.5 重定向的风险
      • 04.跨域资源共享(CORS)
        • 4.1 同源策略是什么
        • 4.2 为什么需要CORS
        • 4.3 简单请求和预检请求
        • 4.4 CORS头部字段
      • 05.HTTP连接优化深度
        • 5.1 TCP连接的代价
        • 5.2 连接的状态管理
        • 5.3 HTTP/2帧与流设计
        • 5.4 HTTP/2服务器推送
        • 5.5 HTTP/3与QUIC
      • 06.WebSocket连接设计
        • 6.1 HTTP的单向限制
        • 6.2 WebSocket握手过程
        • 6.3 WebSocket帧格式
        • 6.4 心跳与保活机制
      • 07.连接安全与可靠性
        • 7.1 连接劫持与防护
        • 7.2 HSTS强制安全连接
        • 7.3 连接异常处理
        • 7.4 连接性能监控
      • 08.综合案例:从HTTP/1.0短连接到HTTP/3 QUIC的四次进化
        • 8.1 案例背景与目标
        • 8.2 第一代:HTTP/1.0短连接——能跑就行
        • 8.3 第二代:HTTP/1.1+Keep-Alive+连接池——复用为王
        • 8.4 第三代:HTTP/2多路复用——并发不再排队
        • 8.5 第四代:WebSocket实时+HTTP/3 QUIC——终极形态
        • 8.6 四种方案横向对比
        • 8.7 案例升华:HTTP连接设计的进化哲学
        • 8.8 全文知识图谱回顾
      • 09.思考题与作业
        • 9.1 基础思考题
        • 9.2 进阶思考题
        • 9.3 动手作业
    • HTTP代理和缓存设计
    • 如何去排查网络故障
    • WebSocket实时通信
    • HTTP3与QUIC协议
  • 操作系统

  • 数据库原理

  • 计算机
  • 网络协议
杨充
2021-01-07
目录

HTTP连接和跳转

# 13.HTTP连接和跳转

# 目录介绍

  • 01.工作案例引入
    • 1.1 一个资讯App的连接优化之路
    • 1.2 问题背后的HTTP连接知识图谱
  • 02.HTTP连接管理
    • 2.1 短连接的问题
    • 2.2 长连接(Keep-Alive)
    • 2.3 连接池设计
    • 2.4 HTTP/2多路复用
    • 2.5 HTTP管线化
  • 03.HTTP重定向机制
    • 3.1 什么是重定向
    • 3.2 重定向状态码
    • 3.3 永久重定向vs临时重定向
    • 3.4 重定向的应用场景
    • 3.5 重定向的风险
  • 04.跨域资源共享(CORS)
    • 4.1 同源策略是什么
    • 4.2 为什么需要CORS
    • 4.3 简单请求和预检请求
    • 4.4 CORS头部字段
  • 05.HTTP连接优化深度
    • 5.1 TCP连接的代价
    • 5.2 连接的状态管理
    • 5.3 HTTP/2帧与流设计
    • 5.4 HTTP/2服务器推送
    • 5.5 HTTP/3与QUIC
  • 06.WebSocket连接设计
    • 6.1 HTTP的单向限制
    • 6.2 WebSocket握手过程
    • 6.3 WebSocket帧格式
    • 6.4 心跳与保活机制
  • 07.连接安全与可靠性
    • 7.1 连接劫持与防护
    • 7.2 HSTS强制安全连接
    • 7.3 连接异常处理
    • 7.4 连接性能监控
  • 08.综合案例:从HTTP/1.0短连接到HTTP/3 QUIC的四次进化
    • 8.1 案例背景与目标
    • 8.2 第一代:HTTP/1.0短连接(能跑就行)
    • 8.3 第二代:HTTP/1.1+Keep-Alive+连接池(复用为王)
    • 8.4 第三代:HTTP/2多路复用(并发不再排队)
    • 8.5 第四代:WebSocket实时+HTTP/3 QUIC(终极形态)
    • 8.6 四种方案横向对比
    • 8.7 案例升华:HTTP连接设计的进化哲学
    • 8.8 全文知识图谱回顾
  • 09.思考题与作业
    • 9.1 基础思考题
    • 9.2 进阶思考题
    • 9.3 动手作业

# 01.工作案例引入

# 1.1 一个资讯App的连接优化之路

场景:小赵是一名移动端工程师,负责公司资讯 App 的首页信息流。产品需求很清晰——下拉刷新加载 20 条新闻,每条包含标题、摘要、封面图。小赵用 Retrofit + OkHttp 很快实现了第一版。

测试环境一切正常。但发给内部体验群后,反馈接踵而至。

问题 ① —— "刷一次首页要 3 秒,等得烦躁":用户反馈下拉刷新特别慢。小赵用 Charles 抓包发现,每次刷新发出了 20 个图片请求和 1 个 API 请求,总共建立了 21 个 TCP 连接。每个连接都需要三次握手(~30ms)和可能的 TLS 握手(~60ms),这些开销叠加起来远超数据本身的传输时间。

问题 ② —— "切了 WiFi 之后,聊天消息收不到了,要杀掉 App 重开才行":App 里有一个用 WebSocket 实现的实时聊天功能。用户反馈从 4G 切换到 WiFi 后,聊天消息就不再推送了。小赵排查发现——WebSocket 底层依赖的 TCP 连接还绑在 4G 的 IP 上,切 WiFi 后 IP 变了但连接没断(TCP 层面没感知),服务端继续往旧 IP 发数据。

问题 ③ —— "上了一个新域名后,老用户打不开网页,报'重定向次数过多'":运维将 old-news.com 301 重定向到 new-news.com,又配置了 new-news.com 302 重定向到某个 CDN 地址。结果部分浏览器进入重定向循环——A→B→A→B...。浏览器的 20 次重定向上限被触发,页面直接报错。

问题 ④ —— "前端部署到 CDN 后跨域报错,后端改了 CORS 配置也没用":前端部署到 cdn.news.com,后端 API 在 api.news.com。小赵在后端加了 Access-Control-Allow-Origin: *,但前端请求仍然报 CORS 错误。排查发现——前端还设置了一个自定义头 X-Device-Id,触发了浏览器的 CORS 预检(OPTIONS 请求),但后端没有处理 OPTIONS 请求。

问题 ⑤ —— "运维说服务器端口资源快耗尽了,netstat 看到几万个 TIME_WAIT":小赵的 OkHttp 没有配置连接池(用了默认的每次新建连接),每次请求完成后主动关闭连接。在高 QPS 下,短时间内产生大量短连接——每个连接关闭后在 TIME_WAIT 状态滞留 60 秒,高峰期累积了几万个 TIME_WAIT 连接。

疑惑链条:

  • "21 个请求为什么这么慢?" → 每个请求一个 TCP 连接 → 21 次握手 → 但一个页面加载只需要 1~2 个持久连接就够了 → HTTP/1.1 Keep-Alive + 连接池
  • "为什么切 WiFi 后 WebSocket 会断开?" → TCP 连接绑定在旧 IP 上 → IP 变了连接也废了 → 需要应用层检测网络变化并重新握手 → HTTP/3 QUIC 用 Connection ID 天然支持连接迁移
  • "重定向为什么循环?" → 301→302→CDN→再触发301 ——重定向链没有收敛 → 需要设计清晰的重定向拓扑
  • "为什么加了 CORS 头还是报错?" → 预检请求(OPTIONS)没处理 → 非简单请求必须先过预检 → CORS 预检缓存(max-age)可以减少预检次数
  • "几万个 TIME_WAIT 是哪来的?" → 短连接 + 高 QPS → 每个连接关闭后都有 TIME_WAIT → 连接池复用连接可以彻底消除 TIME_WAIT

小赵这一串问题,本质都是在问:HTTP 连接到底有多少种状态?短连接和长连接差在哪?WebSocket 和 HTTP/2 的"连接"有什么不同?如何管理好连接才能又快又稳?——这正是"HTTP 连接和跳转"要回答的。

# 1.2 问题背后的HTTP连接知识图谱

把这次优化翻译成 HTTP 连接知识语言:

App 请求 api.news.com 的连接生命周期:

OkHttp 发起请求
    ↓ ① 从连接池取连接(如果有可复用的)
连接池                               ← 问题①⑤的发生地
    ↓ 未命中 → 新建TCP连接 → TLS握手
    ↓ ② 发送HTTP请求
    ↓ ③ 接收HTTP响应
    ↓ ④ 判断是否Keep-Alive
    是 → 归还连接池(等待复用)
    否 → 关闭连接 → TIME_WAIT(60秒)
    ↓
WebSocket 升级连接                     ← 问题②的发生地
    升级后:长连接 → 心跳维护 → 数据双向推送
    ↓ 网络切换
    IP变化 → 连接断裂 → 需要重连
    ↓
重定向链                              ← 问题③的发生地
    301 → 302 → CDN → 循环
    ↓
跨域预检                              ← 问题④的发生地
    OPTIONS → CORS头 → 实际请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

五类问题与后续章节的映射关系:

问题 症状 根因所在的知识点 对应章节
① 刷新3秒 短连接 → Keep-Alive+连接池 02.连接管理
② 切WiFi断连 TCP绑定IP → 连接迁移QUIC 06.心跳保活、05.HTTP/3
③ 重定向循环 301/302混用 → 重定向风险 03.重定向机制
④ CORS预检失败 自定义头→OPTIONS预检 04.跨域资源共享
⑤ 几万TIME_WAIT 短连接 → 连接池复用 02.连接池、05.连接状态

本章的主线就是沿着这五类问题,一层一层拆解 HTTP 连接从建立到关闭的完整生命周期。读完之后,你不仅能优化这些连接问题,还能理解为什么 HTTP/2 用一个连接就够了、为什么 WebSocket 握手是 HTTP 升级、为什么 QUIC 要在 UDP 上重新造一套传输层。

# 02.HTTP连接管理

# 2.1 短连接的问题

疑惑:HTTP/1.0默认每次请求都新建TCP连接,一个网页可能有几十个资源(HTML、CSS、JS、图片),那岂不是要建立几十次TCP连接?

答疑:没错,这正是HTTP/1.0的性能瓶颈。每次TCP连接都需要三次握手(~1.5 RTT)和四次挥手(~2 RTT),加上TCP慢启动机制(新连接的初始窗口很小),短连接的效率极低。

加载一个包含10个资源的网页:
短连接模式:10次TCP握手 + 10次请求响应 + 10次TCP挥手
           = 至少 10 × (1.5 + 1 + 2) × RTT = 45 RTT

长连接模式:1次TCP握手 + 10次请求响应 + 1次TCP挥手  
           = 1.5 + 10 + 2 = 13.5 RTT
1
2
3
4
5
6

回到问题①和⑤:小赵的 OkHttp 没有配置连接池,每次请求都新建 TCP 连接。21 个请求 × 30ms TCP握手 = 630ms 额外耗时。加上每个连接关闭后的 TIME_WAIT 占用,高并发下几万个 TIME_WAIT 占满了端口资源。

# 2.2 长连接(Keep-Alive)

HTTP/1.1默认开启长连接。通过 Connection: keep-alive 头部,TCP连接在请求完成后不立即关闭,可被后续请求复用。

# 请求
GET /index.html HTTP/1.1
Host: example.com
Connection: keep-alive

# 响应
HTTP/1.1 200 OK
Connection: keep-alive
Keep-Alive: timeout=5, max=100
1
2
3
4
5
6
7
8
9
  • timeout=5:空闲5秒后关闭连接
  • max=100:最多复用100次请求

服务端配置示例(Nginx):

keepalive_timeout 65;      # 长连接超时时间
keepalive_requests 1000;   # 单连接最大请求数
1
2

# 2.3 连接池设计

在客户端(如HTTP客户端库),通常维护一个连接池来管理长连接:

连接池设计要素:
├── 最大连接数(防止资源耗尽)
├── 每个主机的最大连接数(防止单站点占用过多连接)
├── 空闲连接超时(回收长时间不用的连接)
├── 连接健康检查(检测已断开的连接)
└── 连接复用策略(FIFO/LIFO)
1
2
3
4
5
6

各语言/框架的连接池实现:

  • Java OkHttp:ConnectionPool,默认最多5个空闲连接,5分钟超时
  • Go net/http:Transport,默认MaxIdleConnsPerHost=2
  • Python requests:Session 对象自带连接池

回到问题①和⑤的解决方案:配置 OkHttp 的连接池 ConnectionPool(5, 5, TimeUnit.MINUTES)——最多保留 5 个空闲连接,5 分钟超时。这样 21 个请求共用 1~2 个 TCP 连接,TIME_WAIT 从几万降到几十。

# 2.4 HTTP/2多路复用

疑惑:HTTP/1.1的长连接虽然复用了TCP连接,但请求仍然是串行的(必须等上一个响应返回才能发下一个请求),这是否仍然存在效率问题?

答疑:是的,这就是著名的"队头阻塞"(Head-of-Line Blocking)问题。HTTP/2通过多路复用彻底解决了它。

HTTP/1.1(串行):        HTTP/2(多路复用):
请求1 → 响应1              请求1 ─┐
请求2 → 响应2              请求2 ─┼── 同一个TCP连接,交错传输
请求3 → 响应3              请求3 ─┘
1
2
3
4

HTTP/2将每个请求-响应对抽象为一个"流"(Stream),每个流有唯一的ID。同一个TCP连接上可以同时存在多个流,数据以"帧"(Frame)为单位交错发送。接收方根据帧的流ID重新组装完整的请求或响应。

# 2.5 HTTP管线化

HTTP/1.1还引入了**管线化(Pipelining)**的概念:客户端不用等上一个请求的响应返回,就可以发送下一个请求。

无管线化(串行):
  请求1 → 等待 → 响应1 → 请求2 → 等待 → 响应2

管线化:
  请求1 → 请求2 → 请求3 →
  ← 响应1 ← 响应2 ← 响应3
1
2
3
4
5
6

疑惑:管线化听起来很好,为什么实际上很少被使用?

答疑:管线化虽然允许连续发送请求,但响应必须按照请求顺序返回。如果第一个请求的处理很慢,后面所有响应都要等待。这就是**队头阻塞(Head-of-Line Blocking)**问题。因此主流浏览器默认禁用了HTTP管线化。真正解决队头阻塞问题的是HTTP/2的多路复用。

# 03.HTTP重定向机制

# 3.1 什么是重定向

重定向是服务器告诉客户端:"你要的资源不在这里,去另一个地址找"。客户端收到重定向响应后,会自动向新地址发起请求。

# 客户端请求
GET /old-page HTTP/1.1
Host: example.com

# 服务器响应(重定向)
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page

# 客户端自动跟随,发起新请求
GET /new-page HTTP/1.1
Host: example.com
1
2
3
4
5
6
7
8
9
10
11

# 3.2 重定向状态码

状态码 名称 类型 方法是否改变 缓存
301 Moved Permanently 永久重定向 可能变为GET 可缓存
302 Found 临时重定向 可能变为GET 默认不缓存
303 See Other 临时重定向 总是变为GET 不缓存
307 Temporary Redirect 临时重定向 不改变 不缓存
308 Permanent Redirect 永久重定向 不改变 可缓存

# 3.3 永久重定向vs临时重定向

永久重定向(301/308):告诉客户端和搜索引擎"这个旧地址永久废弃了,以后请直接访问新地址"。浏览器会缓存这个重定向,下次直接访问新地址。

临时重定向(302/307):告诉客户端"这次去新地址,但以后还是来旧地址"。浏览器不会缓存,每次都先访问旧地址。

# 3.4 重定向的应用场景

  1. HTTP→HTTPS升级:http://example.com → https://example.com(301)
  2. 域名迁移:old-domain.com → new-domain.com(301)
  3. 登录跳转:未登录用户 → 登录页面(302)
  4. 短链接服务:t.cn/xxx → 实际长链接(301或302)
  5. A/B测试:随机重定向到不同版本的页面(302)

# 3.5 重定向的风险

  1. 重定向循环:A→B→A→B...,浏览器通常会在检测到循环后停止(Chrome限制20次)。回到问题③:小赵把 old-news.com 301 到 new-news.com,又配置了 new-news.com 302 到 CDN,CDN 回源时又触发了 301——形成了循环。
  2. 性能损耗:每次重定向增加一次RTT,应尽量减少重定向层数。
  3. SEO影响:滥用302会导致搜索引擎无法正确索引页面。
  4. 开放重定向漏洞:如果重定向目标URL来自用户输入且未验证,可能被用于钓鱼攻击。

# 04.跨域资源共享(CORS)

# 4.1 同源策略是什么

同源是指两个URL的协议、域名、端口三者完全相同。浏览器的同源策略(Same-Origin Policy)限制了不同源的页面之间的资源访问。

https://api.example.com:443/data

协议: https
域名: api.example.com  
端口: 443
1
2
3
4
5

以 https://www.example.com 为基准:

URL 是否同源 原因
https://www.example.com/page2 同源 路径不同,不影响
http://www.example.com 不同源 协议不同
https://api.example.com 不同源 域名不同
https://www.example.com:8080 不同源 端口不同

# 4.2 为什么需要CORS

同源策略保护了用户安全,但也阻止了合法的跨域请求。CORS(Cross-Origin Resource Sharing)就是在保持安全性的前提下,允许受控的跨域访问。

# 4.3 简单请求和预检请求

简单请求:满足特定条件的请求(GET/HEAD/POST、特定Content-Type等),浏览器直接发送,服务器通过响应头控制是否允许。

预检请求:不满足简单请求条件的,浏览器先发送一个OPTIONS预检请求,询问服务器"我是否可以发送这个跨域请求?"

预检请求流程:
1. 浏览器发送OPTIONS请求(预检)
   Origin: https://app.example.com
   Access-Control-Request-Method: PUT
   Access-Control-Request-Headers: Content-Type

2. 服务器返回允许的信息
   Access-Control-Allow-Origin: https://app.example.com
   Access-Control-Allow-Methods: GET, PUT, POST
   Access-Control-Max-Age: 86400  ← 预检结果缓存24小时

3. 浏览器确认允许后,发送实际请求
1
2
3
4
5
6
7
8
9
10
11
12

回到问题④:前端加了自定义头 X-Device-Id,这不是"简单请求"的允许范围,触发了 OPTIONS 预检。后端虽然加了 Access-Control-Allow-Origin: *,但没有处理 OPTIONS 请求。解决:后端统一处理 OPTIONS 请求并返回正确的 CORS 头;或前端避免使用会触发预检的自定义头。

# 4.4 CORS头部字段

响应头 作用 示例
Access-Control-Allow-Origin 允许的来源 * 或 https://app.example.com
Access-Control-Allow-Methods 允许的HTTP方法 GET, POST, PUT, DELETE
Access-Control-Allow-Headers 允许的请求头 Content-Type, Authorization
Access-Control-Max-Age 预检结果缓存时间(秒) 86400
Access-Control-Allow-Credentials 是否允许携带Cookie true

# 05.HTTP连接优化深度

# 5.1 TCP连接的代价

每建立一个TCP连接,背后隐藏着大量的系统资源消耗:

TCP连接的资源开销:

1. 时间开销
   三次握手:1.5 RTT(SYN → SYN-ACK → ACK)
   TLS握手:1~2 RTT(如果使用HTTPS)
   慢启动:新连接的初始拥塞窗口小,需要多个RTT才能达到最优速度
   
2. 内存开销
   每个TCP连接需要维护:
   ├── 发送缓冲区(通常 16KB~256KB)
   ├── 接收缓冲区(通常 16KB~256KB)
   ├── TCP控制块(约 300~700 字节)
   └── Socket结构体

3. 文件描述符开销
   每个连接占用一个文件描述符
   Linux默认限制 1024 个

4. CPU开销
   连接建立和断开的系统调用、内核中断处理、数据的拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

疑惑:既然TCP连接开销这么大,为什么HTTP/2只用一个连接就够了?

答疑:HTTP/2的多路复用让一个连接承载了所有请求。好处:只需一次握+1次TLS;TCP慢启动只需经历一次;但也有风险:唯一的TCP连接若丢包,所有请求都受影响。这也是HTTP/3换用QUIC的原因。

# 5.2 连接的状态管理

TCP连接有多种状态:

TCP连接状态机:
建立:CLOSED → SYN_SENT → ESTABLISHED(客户端)
断开:ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
1
2
3
状态 大量出现说明 排查方向
SYN_SENT 目标服务器不可达 检查网络连通性
CLOSE_WAIT 程序没正确关闭连接 检查代码close调用
TIME_WAIT 短连接过多 使用连接池——对应问题⑤

# 5.3 HTTP/2帧与流设计

HTTP/2的核心设计是将通信分解为帧和流:

帧类型:DATA(0x0)、HEADERS(0x1)、PRIORITY(0x2)、RST_STREAM(0x3)、
         SETTINGS(0x4)、PUSH_PROMISE(0x5)、PING(0x6)、GOAWAY(0x7)
1
2

流的优先级和依赖:浏览器可以告诉服务器"先发CSS,再发JS,最后发图片",这样页面可以更快地开始渲染。

# 5.4 HTTP/2服务器推送

服务器可以在浏览器还没请求CSS和JS时就主动推送:

传统:请求index.html → 解析 → 请求style.css → 请求app.js(3次)
Server Push:请求index.html → 顺带推送style.css + app.js(1次)
1
2

# 5.5 HTTP/3与QUIC

HTTP/2虽然解决了应用层队头阻塞,但TCP层队头阻塞仍然存在。HTTP/3用QUIC协议替代TCP来解决。这恰好是问题②的终极方案。

HTTP协议演进:
HTTP/1.0 → TCP串行、短连接
HTTP/1.1 → TCP长连接、管线化(有队头阻塞)
HTTP/2   → TCP多路复用(TCP层队头阻塞仍在)
HTTP/3   → QUIC(基于UDP),彻底解决队头阻塞 + 支持连接迁移
1
2
3
4
5
QUIC vs TCP:
               TCP                  QUIC
握手           TCP+TLS握手           一次握手完成(0~1 RTT)
队头阻塞       有                    无(流独立)
连接迁移       IP变化需重连          基于Connection ID,WiFi切4G不断
拥塞控制       内核实现              用户态可插拔
1
2
3
4
5
6

回到问题②的解决方案:用 QUIC 替代 TCP 后,连接通过 Connection ID 标识而非四元组,IP 变化时连接不中断。在应用层,WebSocket 配合监听网络变化事件 NetworkCallback.onAvailable(),检测到网络切换后主动重连。

# 06.WebSocket连接设计

# 6.1 HTTP的单向限制

HTTP是一个请求-响应模型。对于实时应用(聊天、股票行情、在线游戏),服务器不能主动推送是致命限制。

在WebSocket出现之前,替代方案有短轮询(浪费带宽)、长轮询(服务端hold住请求)、SSE(单向推送)。WebSocket通过HTTP升级机制建立全双工连接。

# 6.2 WebSocket握手过程

客户端发送HTTP升级请求:
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

服务端同意升级:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

之后的通信就不再是HTTP,而是WebSocket二进制帧协议
1
2
3
4
5
6
7
8
9
10
11
12

# 6.3 WebSocket帧格式

opcode含义:0x1文本帧、0x2二进制帧、0x8关闭连接、0x9 Ping(心跳)、0xA Pong(心跳响应)。

疑惑:为什么客户端发送的数据必须使用掩码(Masking),而服务端不需要?

答疑:掩码的目的是防止缓存投毒攻击——恶意JavaScript可能构造特定字节序列让中间HTTP代理误认为合法HTTP响应并缓存下来。

# 6.4 心跳与保活机制

WebSocket长连接需要心跳机制来保持活性:

心跳配置推荐:
  间隔:30~60秒
  超时判定:连续3次无响应
  重连策略:指数退避(1s, 2s, 4s, 8s, 最大30s)
1
2
3
4

回到问题②:小赵的 WebSocket 没有监听网络变化事件,也没有设置合理的心跳间隔。心跳间隔设了 300 秒(太长),网络切换后要等 5 分钟才发现连接已断。把心跳缩短到 30 秒,并搭配 NetworkCallback 监听网络变化主动重连。

# 07.连接安全与可靠性

# 7.1 连接劫持与防护

常见劫持方式:运营商劫持、DNS劫持、WiFi热点劫持。防护:HTTPS + HSTS + Certificate Pinning + DNS over HTTPS。

# 7.2 HSTS强制安全连接

HSTS告诉浏览器:以后访问这个网站必须使用HTTPS。

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

首次访问 http:// → 301重定向 → https:// → 收到HSTS → 后续自动HTTPS
1
2
3

# 7.3 连接异常处理

常见异常:连接超时、读超时、连接重置、半开连接、连接泄漏。重试策略推荐指数退避+随机抖动。

# 7.4 连接性能监控

指标 健康阈值
DNS解析时间 < 50ms
TCP连接时间 < 100ms
TLS握手时间 < 200ms
TTFB < 500ms
连接复用率 > 80%

Chrome DevTools → Network → Timing 可查看各阶段耗时。

# 08.综合案例:从HTTP/1.0短连接到HTTP/3 QUIC的四次进化

前面我们分别讲了 HTTP 连接管理、重定向、CORS、TCP 代价、HTTP/2 多路复用、WebSocket 和连接安全。但这些知识如果是孤立地看,很难形成"连接优化"的系统思维。

本章用一个贯穿全文的实战案例——一个资讯 App 首页加载的优化,从 HTTP/1.0 短连接开始,经历四次进化,最终达到毫秒级加载。读完这一节,你应该能形成"看到一个页面加载慢就能分析出连接层面的瓶颈和优化路径"的能力。

# 8.1 案例背景与目标

资讯 App 首页信息流:1 个 API 请求 + 20 张封面图。目标:冷启动加载时间从 3000ms 降到 500ms 以内。

版本 协议 关键优化 解决问题
V1 HTTP/1.0短连接 无 ①⑤
V2 HTTP/1.1+连接池+Keep-Alive 复用连接 ①⑤
V3 HTTP/2多路复用 并发无阻塞 ①
V4 WebSocket实时+HTTP/3 全双工+连接迁移 ②③④

# 8.2 第一代:HTTP/1.0短连接——能跑就行

// V1: OkHttp 默认配置(没有连接池)
OkHttpClient client = new OkHttpClient();

// 每次请求一个资源,OkHttp默认行为:
// 请求1 → 新建TCP连接 → 请求 → 关闭连接 → TIME_WAIT
// 请求2 → 新建TCP连接 → 请求 → 关闭连接 → TIME_WAIT
// ...
1
2
3
4
5
6
7
V1 加载分析(21个请求 = 1 API + 20 图片):

连接建立:21次TCP握手 × 30ms = 630ms
TLS握手: 21次 × 60ms = 1260ms(如果有HTTPS)
慢启动:  每个新连接初始窗口14KB → 多RTT才能达到全速
TIME_WAIT:稳态约 21×60/1 = 1260个(QPS=1时)

总耗时:~3000ms(问题①的根因)

瓶颈:每次请求新建TCP连接,时间全耗在握手和慢启动上
对应第5.1节——TCP连接的代价
1
2
3
4
5
6
7
8
9
10
11

# 8.3 第二代:HTTP/1.1+Keep-Alive+连接池——复用为王

// V2: 配置连接池
OkHttpClient client = new OkHttpClient.Builder()
    .connectionPool(new ConnectionPool(
        5,                          // 最多5个空闲连接
        5, TimeUnit.MINUTES         // 5分钟超时
    ))
    .protocols(Arrays.asList(Protocol.HTTP_1_1))
    .build();

// 连接池效果:
// 请求1 → 新建连接 → 请求 → 放回连接池
// 请求2 → 复用连接池中的连接 → 请求 → 放回
// ...
// 最多1~2个TCP连接复用所有21个请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
V2 加载分析:

连接建立:1~2次TCP握手 × 30ms = ~45ms(减少93%!)
TLS握手: 1~2次 × 60ms = ~90ms
后续请求:0次握手(复用)
慢启动:  只需一次TCP慢启动 → 后续请求都在全速窗口

工具:    max-idle-connections = 5
      keep-alive-duration = 5分钟
      单个连接复用次数多,TIME_WAIT几乎消失(问题⑤解决)

总耗时:~800ms(比V1快3.75倍!)

瓶颈:HTTP/1.1串行请求——20张图片排队
虽然复用了连接,但同一连接上请求是串行的
对应第2.4节——HTTP/1.1队头阻塞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 8.4 第三代:HTTP/2多路复用——并发不再排队

// V3: 启用HTTP/2
OkHttpClient client = new OkHttpClient.Builder()
    .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES))
    .protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1))
    // HTTP/2 只需要一个连接
    .build();

// Nginx 服务端
server {
    listen 443 ssl http2;   # 启用HTTP/2
}
1
2
3
4
5
6
7
8
9
10
11
V3 加载分析:

连接建立:1次TCP握手 + 1次TLS握手 = ~90ms(仅1次!)
多路复用:1个API请求+20个图片请求在同一个TCP连接上并发发送
流优先级:API的流优先级最高 → 先收到JSON → 解析出图片URL
图片的流优先级较高 → 尽快填充首屏

加载时间线:
  0ms    → TCP+TLS握手
  90ms   → 发API请求(Stream1) + 发图片请求(Stream3~22)
  120ms  → API响应返回 → 开始渲染界面
  180ms  → 前几张图片到达 → 首屏渲染完成
  350ms  → 所有20张图片加载完成

总耗时:~350ms(比V2快2.3倍,比V1快8.6倍!)

瓶颈:唯一的TCP连接若丢包 → 所有流都被阻塞 → TCP层队头阻塞
对应第5.5节——HTTP/2 over TCP的固有局限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 8.5 第四代:WebSocket实时+HTTP/3 QUIC——终极形态

V4 架构设计:

资讯首页(一次性数据):HTTP/3 over QUIC
  ├── TCP三次握手:0次(QUIC基于UDP)
  ├── 连接建立:0~1 RTT
  ├── 多路复用:各流独立丢包不互相影响
  └── 连接迁移:WiFi切4G不断连(问题②的终极方案)

实时消息推送:WebSocket over HTTP/2(或直接WebTransport)
  ├── 双向通信:服务端可主动推送
  ├── 心跳检测:30秒间隔
  └── 网络切换监听:NetworkCallback自动重连
1
2
3
4
5
6
7
8
9
10
11
12
# Nginx 配置(HTTP/3需要nginx-quic构建或第三方模块)
# 当前HTTP/3主流方案:直接用支持QUIC的CDN
# 或使用 Caddy(原生HTTP/3支持)
caddy {
    servers {
        protocol {
            experimental_http3
        }
    }
    header Strict-Transport-Security "max-age=63072000"
}
1
2
3
4
5
6
7
8
9
10
11
V4 加载分析:

HTTP/3 over QUIC:
  └── 0-RTT握手:若之前连接过 → 0ms握手延迟
  └── 独立流:API流丢失一包不影响图片流
  └── 连接迁移:Connection ID标识连接,IP变化无感知

WebSocket:
  └── 30秒心跳 + NetworkCallback → 网络切换2秒内重连
  └── 适合推送评论/点赞/系统通知等实时消息

总耗时:~120ms(冷启动)/ ~60ms(热启动/0-RTT)

优化效果总结:
  V1→V2:3倍(连接复用)
  V2→V3:2.3倍(多路复用)
  V3→V4:3倍(QUIC 0-RTT + 无TCP队头阻塞)
  总计V1→V4:约25倍提升!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 8.6 四种方案横向对比

维度 V1 HTTP/1.0短连接 V2 HTTP/1.1连接池 V3 HTTP/2多路复用 V4 WebSocket+HTTP/3
TCP连接数(21请求) 21个 1~2个 1个 1个QUIC连接
握手耗时 21×90ms=1890ms 2×90ms=180ms 1×90ms=90ms 0~30ms(0-RTT)
队头阻塞 TCP+HTTP HTTP层 TCP层 无
首页加载总耗时 ~3000ms ~800ms ~350ms ~120ms
TIME_WAIT(稳态) 高 极低 极低 无(UDP)
WebSocket实时 ❌ ❌ ❌ ✅
连接迁移 ❌ ❌ ❌ ✅
对应问题 ①⑤ ①⑤ ① ②③④全部解决
对应章节 2.1/5.1 2.2/2.3 2.4/5.3 6.2/5.5
性能进化曲线:

首页加载耗时(ms)
│
│  3000 ████████  V1(HTTP/1.0短连接)
│  800  ███       V2(HTTP/1.1+连接池)
│  350  ██        V3(HTTP/2多路复用)
│  120  █         V4(HTTP/3 QUIC)
│  60   ▌         V4热启动(0-RTT)
└────────────────────────────→

关键跃升:
  V1→V2:连接复用(告别每次握手)
  V2→V3:多路并发(告别排队)
  V3→V4:彻底消除队头阻塞 + 连接迁移
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 8.7 案例升华:HTTP连接设计的进化哲学

经历了四次进化,回头看 HTTP 连接设计的哲学就豁然开朗了:

HTTP连接设计的四个进化主线:

1. 减少连接数
   V1: 每个请求一个连接 → 21个连接
   V2: 每个域名1~2个连接 → 连接池复用
   V3: 每个域名1个连接 → HTTP/2多路复用
   
   核心理念:连接越少 = 握手越少 = TIME_WAIT越少 = 性能越好

2. 消除队头阻塞
   V1: TCP+HTTP双重队头阻塞
   V2: HTTP层队头阻塞(串行请求)
   V3: TCP层队头阻塞(多路复用受TCP影响)
   V4: 无队头阻塞(QUIC流独立)
   
   核心理念:队头阻塞是"串行化"的代价,并发粒度越小越好

3. 连接健壮性
   V1-V3: TCP连接绑定IP → IP变化=连接断裂
   V4: QUIC用Connection ID → 连接"漂移"不断
   
   核心理念:连接应该跟随设备,而不是跟随IP

4. 从请求-响应到全双工
   V1-V3: 纯请求-响应 → 服务器不能主动推送
   V4: WebSocket + HTTP/3 → 双向自由通信
   
   核心理念:HTTP从"问答"进化到了"对话"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

连接优化的checklist:

客户端侧:
☑ 启用连接池(OkHttp ConnectionPool / Go Transport)
☑ 配置合理的Keep-Alive超时(服务端和客户端两端都要配)
☑ 启用HTTP/2(只需服务端 listen 443 ssl http2)
☑ WebSocket设置30秒心跳 + 网络切换监听

服务端侧:
☑ Nginx: keepalive_timeout 65s; keepalive_requests 1000;
☑ 启用HTTP/2(http2 on;)
☑ HTTP重定向:HTTP→HTTPS用301(不需要回退)
☑ CORS: 正确处理OPTIONS预检(尤其自定义头场景)
☑ 用CDN承载HTTP/3(QUIC)入口
1
2
3
4
5
6
7
8
9
10
11
12

# 8.8 全文知识图谱回顾

                    小赵的五类问题
                    │
    ┌───────┬───────┼───────┬───────┐
    │       │       │       │       │
   ①慢     ②断连   ③循环   ④预检   ⑤TIME
    │       │       │       │       │
    ▼       ▼       ▼       ▼       ▼
 短连接   连接迁移  重定向  CORS预检  TIME_WAIT
 Keep-   QUIC    301/302  OPTIONS  连接池
 Alive   [5.5]   [3.2]    [4.3]    [5.2]
 [2.1]     │       │       │       │
    │       │       │       │       │
    └───────┴───────┼───────┴───────┘
                    │
        ┌───────────┴───────────┐
        │                       │
 HTTP/2多路复用 [2.4/5.3]   WebSocket [6章]
 帧+流+优先级+推送         握手+心跳+保活
        │                       │
        └───────────┬───────────┘
                    │
  V1→V2→V3→V4  HTTP连接的四次进化
   [第8章]      3000ms→120ms
                    │
                    ▼
  减少连接 / 消除阻塞 / 连接健壮 / 全双工
    HTTP连接设计的四大进化哲学 [8.7节]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

最终的方法论沉淀——优化 HTTP 连接时,都应该问自己三个问题:

  1. 能复用吗?(连接池配了吗?Keep-Alive 超时合适吗?→ 少建连接就是快)
  2. 能并发吗?(HTTP/2 开了吗?多路复用了没?→ 别让请求排队)
  3. 能健壮吗?(心跳有了吗?网络切换检测了吗?TIME_WAIT 多吗?→ 稳定压倒一切)

把这三个问题问到位,你就从"会发请求"进化到了"懂 HTTP 连接优化的工程师"。

# 09.思考题与作业

# 9.1 基础思考题

  1. 短连接 vs 长连接的量化:一个页面有 50 个资源,每个资源 50KB。假设 TCP 握手 30ms,TLS 握手 60ms,RTT=10ms。分别计算短连接和长连接的总耗时差。多出来的时间主要来自哪些环节?

  2. 301 vs 302 的选择:以下场景分别应该用 301 还是 302?为什么?① HTTP→HTTPS 升级 ② 登录后跳转 ③ 域名永久迁移 ④ 短链接服务

  3. 简单请求 vs 预检请求:以下哪些跨域请求会触发 CORS 预检?① GET /api/data ② POST /api/data with Content-Type: application/json ③ DELETE /api/users/123 ④ GET /api/data with X-Custom-Header: value

  4. TIME_WAIT 的意义:为什么 TCP 要有 TIME_WAIT 状态?如果去掉 TIME_WAIT,什么场景下会出问题?

# 9.2 进阶思考题

  1. 问题①的优化收益分解:V1→V4 的 25 倍提升中,每一项优化各贡献了多少倍?请按"连接复用→多路复用→QUIC 0-RTT"分别拆分估算。

  2. HTTP/2 单一连接的隐患:HTTP/2 多路复用让一个域名只需要一个连接,但这也是一把双刃剑。请列出至少 2 种"只用 1 个连接反而不如 6 个 HTTP/1.1 连接"的场景,并解释为什么。

  3. WebSocket 心跳的工程实践:如果心跳间隔太短(如 5 秒),会有什么问题?如果太长(如 5 分钟),又有什么问题?移动端的心跳间隔应该怎样权衡省电和实时性的矛盾?

  4. QUIC 对 Web 架构的连锁影响:QUIC 基于 UDP 实现可靠传输,这意味着网络中间设备(NAT、防火墙、负载均衡器)的处理方式要改变。请分析 QUIC 对现有的 CDN、负载均衡器、NAT 网关各有什么挑战。

# 9.3 动手作业

作业一(必做):用 Chrome DevTools 分析连接性能。

  • 打开 Chrome DevTools → Network → 选择一个请求 → Timing 标签页。
  • 记录:DNS Lookup、Initial Connection、SSL、TTFB、Content Download 各阶段耗时。
  • 用 chrome://net-internals/#sockets 查看当前浏览器的连接池状态(活跃连接数、空闲连接数)。

作业二(选做):对比不同协议的加载性能。

  • 用 Nginx 搭建一个静态文件服务(20 张小图片 + 1 个 JSON 文件)。
  • 分别配置 HTTP/1.1 和 HTTP/2,用 Chrome DevTools 测量加载耗时。
  • 对比两种协议下:连接数、加载时间、请求并发数。
协议 TCP连接数 加载总耗时 请求是否并发 瀑布图特征
HTTP/1.1
HTTP/2

作业三(架构思考):分析你的项目连接配置。

  • 检查你项目中 HTTP 客户端的连接池配置(OkHttp/axios/requests 等)。
  • 检查 Keep-Alive 超时、最大连接数、是否启用了 HTTP/2。
  • 如果有 WebSocket,检查心跳间隔和重连策略。
  • 写出优化建议和预期收益。
上次更新: 2026/06/09, 15:47:57
HTTPS协议设计策略
HTTP代理和缓存设计

← HTTPS协议设计策略 HTTP代理和缓存设计→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式