---
title: 协议演进与兼容
url: https://doc.liz6.com/networking/14-protocol-design-and-evolution/02-protocol-evolution-and-compatibility
locale: zh
area: networking
tags:
- networking
- 协议设计与演进
date: 2026-06-30
modified: 2026-07-11
description: 协议部署后就很难改了——TCP 固定 header 导致扩展花了十年,TLS 中间件对未知扩展的 buggy 行为导致 ossification。QUIC 从设计之初就内置版本协商和 GREASE(随机插入无意义扩展)对抗中间件僵化——这是所有新协议的必修课。
---

# 协议演进与兼容

> 协议部署后就很难改了——TCP 固定 header 导致扩展花了十年,TLS 中间件对未知扩展的 buggy 行为导致 ossification。QUIC 从设计之初就内置版本协商和 GREASE(随机插入无意义扩展)对抗中间件僵化——这是所有新协议的必修课。

## 概述

协议不是一成不变的——部署后就很难改了。HTTP/1.1 headers 的文本灵活性使其可以无痛添加新特性（Cache-Control, CORS），但 TCP 的固定 header 导致扩展困难（TCP Fast Open 花了 10 年才普及）。QUIC 从设计之初就内置了版本协商和 GREASE（随机插入无意义扩展）——对抗中间件对未知字段的 buggy 行为造成的 protocol ossification。所有新设计的协议都应该实现 GREASE 和明确的扩展点。

## Forward vs Backward

```
Forward compatible (新 client, 旧 server):
  未知字段: skip (protobuf, TLV)
  未知 frame type: ignore (HTTP/2)
  → 实现: "be liberal in what you accept"

Backward compatible (旧 client, 新 server):
  默认值填充
  版本号协商
  capability 位图 (TLS cipher suites)
```

## 协商与 Fallback

<svg viewBox="0 0 720 320" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="三种协议协商机制:HTTP Upgrade、TLS ALPN、HTTP Alt-Svc">
  <defs>
    <marker id="nf-arrow" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
  </defs>
  <rect width="720" height="320" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">协商与 Fallback:三种机制,谁能改当前连接、谁只能改下一次</text>

  <text x="20" y="82" font-size="12" font-weight="700" fill="#1f2933">HTTP Upgrade</text>
  <rect x="140" y="56" width="220" height="44" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="250" y="74" text-anchor="middle" font-size="11" fill="#3730a3">GET / HTTP/1.1</text>
  <text x="250" y="90" text-anchor="middle" font-size="11" fill="#3730a3">+ Upgrade: h2c</text>
  <line x1="364" y1="78" x2="396" y2="78" stroke="#475569" stroke-width="1.6" marker-end="url(#nf-arrow)"/>
  <rect x="400" y="56" width="300" height="44" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="550" y="83" text-anchor="middle" font-size="12" font-weight="600" fill="#115e59">101 Switching 或 ignored</text>

  <text x="20" y="146" font-size="12" font-weight="700" fill="#1f2933">TLS ALPN</text>
  <rect x="140" y="120" width="220" height="44" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="250" y="138" text-anchor="middle" font-size="11" fill="#3730a3">ClientHello</text>
  <text x="250" y="154" text-anchor="middle" font-size="11" fill="#3730a3">ALPN=[h2, http/1.1]</text>
  <line x1="364" y1="142" x2="396" y2="142" stroke="#475569" stroke-width="1.6" marker-end="url(#nf-arrow)"/>
  <rect x="400" y="120" width="300" height="44" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="550" y="147" text-anchor="middle" font-size="12" font-weight="600" fill="#115e59">ServerHello ALPN="h2"</text>

  <text x="20" y="210" font-size="12" font-weight="700" fill="#1f2933">HTTP Alt-Svc</text>
  <rect x="140" y="184" width="220" height="44" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="250" y="210" text-anchor="middle" font-size="11" fill="#3730a3">Alt-Svc: h3=":443"</text>
  <line x1="364" y1="206" x2="396" y2="206" stroke="#475569" stroke-width="1.6" marker-end="url(#nf-arrow)"/>
  <rect x="400" y="184" width="300" height="44" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="550" y="202" text-anchor="middle" font-size="11.5" font-weight="600" fill="#115e59">下一次连接用 HTTP/3</text>
  <text x="550" y="218" text-anchor="middle" font-size="10.5" fill="#0f766e">(不升级当前连接)</text>

  <rect x="40" y="248" width="640" height="54" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="56" y="270" font-size="12.5" fill="#115e59">HTTP Upgrade、TLS ALPN 都是同一连接内的实时协商;Alt-Svc 只是"下次换协议"的提示——</text>
  <text x="56" y="289" font-size="12.5" fill="#115e59">不会升级当前连接,这正是"协商"与"fallback"的区别。</text>
</svg>

## GREASE

```
问题: 中间件 (防火墙, NAT) 只认识它知道的 byte pattern
  → 新 protocol extension → 被丢弃 (不认识) → protocol 无法演进

GREASE: 客户端随机插入无意义的 extensions/values:
  TLS: random cipher suites in ClientHello
  HTTP/2: random SETTINGS parameters
  QUIC: random transport parameters

目的: 中间件必须正确处理 unknown values (ignore, not reject)
  → 未来真正的 extension 不会被中间件阻塞
```

## QUIC 的教训

<svg viewBox="0 0 720 380" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="TCP 的四类历史教训如何应用到 QUIC 设计">
  <defs>
    <marker id="ql-arrow" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
  </defs>
  <rect width="720" height="380" fill="#ffffff"/>
  <text x="360" y="26" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">QUIC 的教训:TCP 的四个老问题,逐个逆向设计</text>

  <rect x="44" y="44" width="310" height="56" rx="6" fill="#e2e8f0"/>
  <text x="58" y="66" font-size="12" font-weight="700" fill="#334155">① Header 明文</text>
  <text x="58" y="84" font-size="10.5" fill="#64748b">→ middlebox 修改 → ossification</text>
  <line x1="354" y1="72" x2="392" y2="72" stroke="#475569" stroke-width="1.6" marker-end="url(#ql-arrow)"/>
  <rect x="392" y="44" width="284" height="56" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="406" y="66" font-size="12" font-weight="700" fill="#115e59">QUIC:flags 全加密</text>
  <text x="406" y="84" font-size="10.5" fill="#0f766e">middlebox 只见 Connection ID</text>

  <rect x="44" y="110" width="310" height="56" rx="6" fill="#e2e8f0"/>
  <text x="58" y="132" font-size="12" font-weight="700" fill="#334155">② 无版本协商</text>
  <text x="58" y="150" font-size="10.5" fill="#64748b">→ 演进缓慢</text>
  <line x1="354" y1="138" x2="392" y2="138" stroke="#475569" stroke-width="1.6" marker-end="url(#ql-arrow)"/>
  <rect x="392" y="110" width="284" height="56" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="406" y="132" font-size="12" font-weight="700" fill="#115e59">QUIC:version 在首包</text>
  <text x="406" y="150" font-size="10.5" fill="#0f766e">服务器可拒绝并提议其他 version</text>

  <rect x="44" y="176" width="310" height="56" rx="6" fill="#e2e8f0"/>
  <text x="58" y="198" font-size="12" font-weight="700" fill="#334155">③ 5-tuple 绑定</text>
  <text x="58" y="216" font-size="10.5" fill="#64748b">→ NAT rebinding → disconnect</text>
  <line x1="354" y1="204" x2="392" y2="204" stroke="#475569" stroke-width="1.6" marker-end="url(#ql-arrow)"/>
  <rect x="392" y="176" width="284" height="56" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="406" y="198" font-size="12" font-weight="700" fill="#115e59">QUIC:Connection ID</text>
  <text x="406" y="216" font-size="10.5" fill="#0f766e">与 IP / port 无关</text>

  <rect x="44" y="242" width="310" height="56" rx="6" fill="#e2e8f0"/>
  <text x="58" y="270" font-size="12" font-weight="700" fill="#334155">④ Extension detection</text>
  <line x1="354" y1="270" x2="392" y2="270" stroke="#475569" stroke-width="1.6" marker-end="url(#ql-arrow)"/>
  <rect x="392" y="242" width="284" height="56" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="406" y="264" font-size="12" font-weight="700" fill="#115e59">QUIC:GREASE 内置</text>
  <text x="406" y="282" font-size="10.5" fill="#0f766e">于每个 QUIC 实现</text>

  <rect x="40" y="310" width="640" height="54" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="56" y="332" font-size="12.5" fill="#3730a3">四类教训的共同逻辑:把明文、无协商、地址绑定、扩展检测这些让 TCP 僵化的设计,</text>
  <text x="56" y="351" font-size="12.5" fill="#3730a3">在 QUIC 里逐一反过来做——这是"协议演进"这门课的必修内容。</text>
</svg>

## 参考

- **GREASE**: RFC 8701
- **QUIC Appendix A**: RFC 9000 (design rationale)

*Keywords: forward compatibility, backward compatibility, GREASE, ossification, QUIC, Alt-Svc*
