---
title: UDP 与 QUIC 传输层
url: https://doc.liz6.com/networking/03-l4-transport-layer/02-udp-and-quic-transport-layer
locale: zh
area: networking
tags:
- networking
- L4传输层
date: 2026-06-30
modified: 2026-07-11
description: UDP 本身只是一个"带端口号的 IP",QUIC 在它之上用 UDP 封装了一套完整的可靠传输:0-RTT 握手、stream multiplexing 无队头阻塞、连接迁移——TCP 花了 30 年学到的东西,QUIC 一次做对。
---

# UDP 与 QUIC 传输层

> UDP 本身只是一个"带端口号的 IP",QUIC 在它之上用 UDP 封装了一套完整的可靠传输:0-RTT 握手、stream multiplexing 无队头阻塞、连接迁移——TCP 花了 30 年学到的东西,QUIC 一次做对。

## 概述

UDP 是最简单的传输层协议——8 字节 header，无连接、无重传、无流控。其"简陋"恰恰是优势：DNS 查询（1 个包往返）、实时音视频（丢几帧不影响体验）、VPN（WireGuard）都选择 UDP。QUIC 在 UDP 之上实现了一个完整的现代传输层，将 TCP 的流控/拥塞控制和 TLS 1.3 加密合并到单一协议中，解决了 TCP 的队头阻塞和连接迁移问题。HTTP/3 即运行在 QUIC 上。

## UDP

```
UDP Header (fixed 8 bytes):

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Source Port          |       Destination Port        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            Length             |           Checksum            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Length: UDP header + payload 总长度 (最小 8 = header only, 最大 65535)
Checksum: IPv4 可选 (0=no checksum), IPv6 强制 — 覆盖 pseudo-header (src/dst IP +
          protocol + length) + UDP header + payload
```

UDP 提供"端口 + 长度 + optional checksum"之外什么都不做——无连接、无重传、无流控、无拥塞控制。应用层如果需可靠性，得自己实现。QUIC 正是"应用层自己实现传输层"的案例。

## QUIC 传输层

QUIC 不是"UDP + 可靠性包装"——它是在 UDP 之上实现的全新传输层，将 TCP 的流控/拥塞控制 和 TLS 的加密认证合并到了一个协议里。

### QUIC Packet 结构

```
QUIC Long Header (Initial/Handshake/0-RTT packets):

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1|   Type  |     Version (32 bits)                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| DCID Len  |  Destination Connection ID (0-20 bytes)          ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SCID Len  |  Source Connection ID (0-20 bytes)               ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

QUIC Short Header (1-RTT data packets):

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|1|S|R|R|R|        Destination Connection ID (...)           ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Header Form (bit 0): 1=Long, 0=Short
Fixed Bit (bit 1): 始终 1 — 用于 version negotiation 检测 (中间件不能设这个 bit)
Spin Bit (bit 2 in Short): 每个 RTT 翻转 1 次 — 观察者可以测量 RTT
Reserved Bits: 0, 中间件如果改这些 bits → GREASE 检测到 → connection close
```

### QUIC 连接: Connection ID

TCP 连接 = 5-tuple (src_ip, dst_ip, src_port, dst_port)。IP 换了 → 连接断开。

QUIC 连接 = Destination Connection ID + Source Connection ID。端点可以随时换 Connection ID (通过 NEW_CONNECTION_ID frame) → IP/port 完全可变。

Connection Migration:

<svg viewBox="0 0 720 390" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="QUIC 连接迁移流程:Connection ID 不变,IP 换了连接不断">
  <defs>
    <marker id="cma" 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="390" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">QUIC 连接迁移:Connection ID 不变,IP 换了连接不断</text>
  <rect x="150" y="48" width="420" height="56" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="69" text-anchor="middle" font-size="13" font-weight="700" fill="#3730a3">客户端 · WiFi</text>
  <text x="360" y="88" text-anchor="middle" font-size="11" fill="#4f46e5">IP 192.168.1.x · QUIC flow 用 CID_A → CID_B 标识连接</text>
  <line x1="360" y1="104" x2="360" y2="142" stroke="#475569" stroke-width="1.8" marker-end="url(#cma)"/>
  <text x="380" y="127" font-size="11" font-weight="700" fill="#334155">switch to 4G(网络切换)</text>
  <rect x="150" y="142" width="420" height="56" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="360" y="163" text-anchor="middle" font-size="13" font-weight="700" fill="#115e59">客户端 · 4G</text>
  <text x="360" y="182" text-anchor="middle" font-size="11" fill="#0f766e">IP 10.10.10.x · 发送 PATH_CHALLENGE(新 IP,同样的 CID_A → CID_B)</text>
  <line x1="360" y1="198" x2="360" y2="236" stroke="#475569" stroke-width="1.8" marker-end="url(#cma)"/>
  <text x="380" y="221" font-size="11" font-weight="700" fill="#334155">Server 响应</text>
  <rect x="150" y="236" width="420" height="56" rx="8" fill="#e0e7ff" stroke="#c7d2fe"/>
  <text x="360" y="257" text-anchor="middle" font-size="13" font-weight="700" fill="#3730a3">PATH_RESPONSE</text>
  <text x="360" y="276" text-anchor="middle" font-size="11" fill="#4338ca">验证新路径可达 → 迁移确认</text>
  <line x1="360" y1="292" x2="360" y2="310" stroke="#475569" stroke-width="1.8" marker-end="url(#cma)"/>
  <rect x="60" y="314" width="600" height="56" rx="8" fill="#dcfce7" stroke="#4ade80"/>
  <text x="76" y="336" font-size="12.5" fill="#166534">连接无缝迁移!应用层看不到任何中断——</text>
  <text x="76" y="356" font-size="12.5" fill="#166534">QUIC 连接靠 Connection ID(非 IP/port)识别,换网不断线。</text>
</svg>

### Stream Multiplexing

一个 QUIC 连接有多个 stream:

```
Stream ID (62-bit, variable-length encoding):
  Bit 0: 0=client-initiated, 1=server-initiated
  Bit 1: 0=bidi, 1=unidirectional
  Bits 2-61: stream number (0, 1, 4, 8, 12, ... — client starts at 0, server at 1,
             bidi-only: even client, odd server)

每个 stream 独立:
  - 有独立的 flow control (MAX_STREAM_DATA / STREAM_DATA_BLOCKED)
  - Stream 1 丢包 → 只阻塞 Stream 1 的数据, Stream 3 继续交付
  - 解决 TCP 队头阻塞: TCP 中所有 stream 的字节在同一个 byte stream → 一个丢包阻塞所有

连接级 flow control: MAX_DATA / DATA_BLOCKED — 限制所有 stream 的总接收量
```

### 0-RTT

```
首次连接:
  1-RTT: ClientHello → ServerHello → EncryptedExtensions → Finished → (data)

后续连接 (PSK mode):
  Client: 缓存上次的 session ticket (含 PSK + transport parameters + server address)
  0-RTT: ClientHello + PSK + early data — 在收到 ServerHello 之前就开始发数据
  Server: 验证 PSK → 接受 early data → 直接处理

0-RTT 安全风险 — Replay:
  攻击者重放 0-RTT 包 → server 重复处理 → 重复副作用 (如扣款两次)

QUIC 缓解:
  - 应用层: 只允许幂等操作 (GET, HEAD, OPTIONS) 在 0-RTT 中
  - 传输层: server 可以发送 REJECTED 帧 → client 必须重发 0-RTT 数据
  - AEAD limits: 同一个 key 不能用于太多 0-RTT 包 → 限制重放窗口
```

### QUIC Loss Detection

QUIC 的丢包检测比 TCP 更精确：

```
TCP: 3 duplicate ACKs → retransmit (但对 SACK, 区分 reordering vs loss)

QUIC: 
  - 每个包有独立的 packet number (单调递增, 即使是重发包也用新号)
  - 接收方 ACK frame: 最大收到 + missing ranges → 精确知道哪些包丢了
  - Loss detection:
    1. Time threshold: 超过 kTimeThreshold (9/8) × latest RTT → 丢包
    2. Packet threshold: 超过 kPacketThreshold (3) 个更大的包被 ACK 了 → 丢包
```

## QUIC 应用

```
HTTP/3: QUIC 的最大用户 (RFC 9114)
DNS/DoQ: DNS over QUIC (RFC 9250) — 0-RTT query
SMB over QUIC: Windows Server 2022
RDP over QUIC: 远程桌面抗丢包
```

## 参考

- **RFC**: 768, 8999-9002, 9000 Appendix A (design rationale), 9250
- **实现**: quiche (Cloudflare), lsquic (LiteSpeed), quinn (Rust), ngtcp2 (C)
- **LWN**: "QUIC and HTTP/3"

*Keywords: UDP, QUIC, connection migration, 0-RTT, stream multiplexing, head-of-line blocking, spin bit, GREASE*
