目录

你有没有想过,当你在玩王者荣耀时,技能释放几乎零延迟;当你用 Chrome 浏览网页时,首次加载快得惊人;当你用某款游戏加速器时,速度比直连还快。

它们背后用的都不是传统 TCP,而是基于 UDP 的自定义传输协议。

很多人知道”UDP 是不可靠的”、“UDP 比 TCP 快”,但真要细问:

既然 UDP 不可靠,那这些协议怎么做到数据不丢的? KCP 和 QUIC 到底有什么不同?什么场景该选哪个? 为什么 Google 要在 UDP 上重新造轮子,而不是直接优化 TCP?

今天这篇文章,我们从最底层出发,先把 UDP 协议本身彻底搞懂,然后系统梳理六大基于 UDP 的可靠传输方案,重点对比 KCP 与 QUIC 的原理和实现细节,最后咱一起研究下到底该选 TCP 还是 UDP 可靠传输。


UDP 协议:简单到极致的传输层协议

UDP 头部结构:只有 8 字节!

在讲”如何让 UDP 变可靠”之前,我们得先理解 UDP 本身长什么样。

                    UDP 数据报格式 (RFC 768)

    0      7 8     15 16    23 24    31
   +--------+--------+--------+--------+
   |     Source      |   Destination   |   源端口 (16位) | 目的端口 (16位)
   |      Port       |      Port        |
   +--------+--------+--------+--------+
   |     Length     |    Checksum      |   长度 (16位) | 校验和 (16位)
   +--------+--------+--------+--------+
   |
   |          data octets ...           |
   |              ...                   |
   +-----------------------------------+

没错,整个头部只有 4 个字段,固定 8 字节。 对比 TCP 的 20~60 字节(20 字节固定头 + 最多 40 字节选项),UDP 简洁到极致。

四字段详解

字段长度(bit)含义说明
源端口(Source Port)16发送方端口号可选字段,设为 0 时表示不需要返回信息
目的端口(Destination Port)16接收方端口号必须填写,用于将数据交给正确的应用进程
长度(Length)16UDP 数据报总长度包含 8 字节首部 + 数据部分,最小值为 8(仅首部无数据)
校验和(Checksum)16错误检测码用于检查传输过程中是否出错

小知识 , 为什么源端口是可选的?

当你用 DNS 查询域名时,客户端发送请求需要填源端口(操作系统自动分配临时端口),因为服务器需要知道把回复发给谁。

但某些场景(如 DHCP 客户端发现),客户端还没有 IP 地址,此时设为 0 表示”我不需要回复”。这就是为什么 UDP 允许省略源端口。

校验和的细节:伪头部(Pseudo-header)

校验和是 UDP 中最值得深挖的字段,它有一个特殊设计,伪头部:

计算校验和时的"伪头部"(不实际传输,仅用于计算):

┌─────────────────────────────────────┐
│         伪头部 (Pseudo-header)       │
├─────────────────┬───────────────────┤
│ Source IP Addr  │ Destination IP Addr│  来自 IP 层头部
├─────────────────┼───────────────────┤
│ Zero (8 bits)   │ Protocol (8 bits) │  协议号 = 17 (UDP)
├─────────────────┴───────────────────┤
│         UDP Length (16 bits)         │  UDP 报文总长度
└─────────────────────────────────────┘
            ↓ 拼接 ↓
┌─────────────────────────────────────┐
│         UDP 真正的首部               │  (8字节)
├─────────────────┬───────────────────┤
│ Src Port(16)    │ Dst Port(16)       │
├─────────────────┼───────────────────┤
│ Length (16)     │ Checksum (16)      │
└─────────────────┴───────────────────┘
            ↓ 拼接 ↓
┌─────────────────────────────────────┐
│             Data (数据)              │
└─────────────────────────────────────┘

校验和 = 对以上所有内容(伪头部 + UDP首部 + Data)求补码和

为什么要引入伪头部? 因为 IP 层本身不做端到端的校验(IP 头部有校验但只覆盖 IP 头)。如果不加伪头部,UDP 无法检测到”数据包被错误路由到另一个地址”的情况,比如路由表出错导致包被送到了错误的主机。

IPv4 vs IPv6 的关键差异:

  • IPv4:校验和是可选项!可以设为全 0 来跳过校验(很多旧系统这样做以提升性能)。接收方看到全 0 就不检查了。
  • IPv6:UDP 校验和是强制的,不可关闭!因为 IPv6 头部本身就没有校验和字段(为了简化路由器处理),所以必须依赖上层协议来做完整性检查。

这意味着在 IPv6 网络中,UDP 的校验和必须正确计算,否则包会被直接丢弃。

UDP 与 TCP 头部大小对比

┌──────────────────────────────────────────────────┐
│                  TCP 头部 (20 ~ 60 字节)            │
│  ┌──────┬──────┬──────┬──────┬──────┬───┬─────────┐│
│  │SrcPt │DstPt │ Seq  │ Ack  │Flags│Wnd│ Options ││  固定20字节 + 选项0~40字节
│  │ 16b  │ 16b  │ 32b  │ 32b  │ 16b │16b│ 0~320b  ││
│  └──────┴──────┴──────┴──────┴──────┴───┴─────────┘│
└──────────────────────────────────────────────────┘
                        vs
┌──────────────────────────────────────────────────┐
│                  UDP 头部 (固定 8 字节)              │
│  ┌──────┬──────┬──────┬──────┐                       │
│  │SrcPt │DstPt │Length│Check │                       │  固定8字节,无任何选项
│  │ 16b  │ 16b  │ 16b  │ 16b  │                       │
│  └──────┴──────┴──────┴──────┘                       │
└──────────────────────────────────────────────────┘

结论:
- UDP 头部: 固定 8 字节 (无变化)
- TCP 头部: 20 ~ 60 字节 (取决于选项)
- UDP 比 TCP 小: 60% ~ 75%!
- 在大量小包场景下,节省的开销非常可观

UDP 的工作原理与五大特性

五大核心特性一览

                    UDP 的五大特性

  ┌─────────────────────────────────────────────────┐
  │                                                   │
  │  ① 无连接    → 不需要握手,想发就发                │
  │  ② 不可靠    → 不确认、不重传、不排序、不流控      │
  │  ③ 低开销    → 仅 8 字节首部,无状态维护开销        │
  │  ④ 广播/多播 → 天然支持一对多通信                  │
  │  ⑤ 面向报文  → 保留应用层消息边界                  │
  │                                                   │
  └─────────────────────────────────────────────────┘
#特性详细说明生活类比
1无连接(Connectionless)不需要像 TCP 那样建立连接,应用层数据下来后直接加上 UDP 头就发出去了寄明信片 vs 打电话,明信片扔进邮筒就走,不需要对方”接电话”确认
2不可靠(Unreliable)不做确认(ACK)、不做重传、不做排序、不做流量控制。发了就是发了,丢没丢不知道广播喊话,喊完不管别人听没听到
3低开销(Low Overhead)仅 8 字节固定首部,无选项字段;无需维护连接状态、滑动窗口、拥塞控制等复杂机制轻装上阵,只带必需品上路
4支持广播/多播(Broadcast/Multicast)利用 UDP 可以向同一网络的所有主机(广播)或一组主机(多播)发送消息组长通知组员,一次说话所有人都能听到
5面向报文(Message-Oriented)保留应用层的消息边界,不会像 TCP 那样合并/拆分数据快递整件送达,发什么收什么,不拆不拼

无连接的工作流程

TCP 的通信流程 (三次握手):
  Client                  Server
    |                        |
    | -------- SYN -------> |  ① 客户端发起连接
    | <---- SYN+ACK ------- |  ② 服务端同意
    | -------- ACK -------> |  ③ 双方确认建立
    | ===== 连接建立完成 === |  (已消耗 1.5 RTT)
    | ---- [数据] ------->  |  ④ 开始传输
    | <--- [ACK] ---------  |

UDP 的通信流程 (无握手):
  Client                  Server
    |                        |
    | ---- [UDP数据] ----->  |  直接发送! 无需任何前置步骤!
    |                        |  服务端收到就处理, 没收到就算了
    | ---- [UDP数据] ----->  |  每个包独立, 互不影响
    |                        |

关键区别:
- TCP: 先建立"管道", 再流水式传输
- UDP: 每个"信封"独立邮寄, 互不相干

面试常考题:UDP 为什么比 TCP 快?

不仅仅是头部小的原因。完整答案是:

  1. 无连接建立:TCP 需要 1~3 次 RTT 握手,UDP 为 0
  2. 无确认重传:TCP 每个包都需要 ACK,超时要重传,UDP 不管
  3. 无拥塞控制:TCP 有慢启动/拥塞避免/快速恢复,UDP 全速发送
  4. 无排序:TCP 要按序交付(可能因等待乱序包而延迟),UDP 收到就交上去
  5. 头部开销小:UDP 8 字节 vs TCP 20~60 字节

但这些”优势”同时也是”劣势”,后面我们讲的 KCP/QUIC,本质上就是在找回那些被丢弃的能力,同时尽量保留低延迟的优势。

UDP 的典型应用场景

了解了 UDP 的特性之后,哪些场景适合用它呢?

协议/应用端口场景说明为什么选 UDP
DNS53域名解析查询包极小(几十字节),追求低延迟,丢了重查就行
DHCP67/68动态 IP 分配客户端刚接入网络时还没有 IP 地址,无法建立 TCP 连接
NTP123网络时间同步时间同步对延迟敏感,且”过期的精确时间”毫无意义
VoIP / RTP随机实时音视频通话迟到的语音包没用,宁可丢包也不能卡顿
在线游戏随机FPS / MOBA / RTS技能释放要求毫秒级响应,可容忍偶尔丢包
TFTP69简单文件传输嵌入式场景,协议极简,适合引导加载
SNMP161网络设备管理管理信息简短,网络故障时仍需可达
QUIC / HTTP/3443现代网页浏览在 UDP 上实现了可靠加密传输(本文重点之一!)

用 tcpdump 抓取真实的 UDP 包

理论讲完了,来点实操。在 Linux/macOS 上你可以用 tcpdump 直接抓取 UDP 包:

# ============================================
# 示例 1: 抓取 DNS 查询的 UDP 包(最常见的 UDP 应用)
# ============================================

# 在终端 A 启动抓包(需要 root 权限)
sudo tcpdump -i any udp port 53 -nn -X -c 5

# 参数解释:
#   -i any    : 监听所有网络接口
#   udp port 53: 只捕获 UDP 53号端口(DNS)
#   -nn       : 不解析域名和服务名(显示原始 IP 和端口)
#   -X        : 以十六进制和 ASCII 同时显示数据内容
#   -c 5      : 只抓 5 个包后就停止

# 在终端 B 执行一次 DNS 查询触发流量
dig www.baidu.com A +short
# 或者用 nslookup
nslookup www.google.com

抓包结果示例(简化展示):

# 抓到的 DNS 请求包 (UDP):
14:23:45.123456 IP 192.168.1.100.54321 > 8.8.8.8.53: 
    0x0000:  4500 003c 1a2b 4000 4011 b8c0 c0a8 0164
    0x0010:  0808 0808 d431 0035 0028 ea9f 0100 0001
    ...
    ↑ 解读:
    - 4500: IPv4 版本4, IHL=5(20字节)
    - 003c: 总长度=60字节 (IP头20 + UDP头8 + DNS查询32)
    - 11:   协议号=17 (UDP!)
    - c0a8 0164: 源IP=192.168.1.100
    - 0808 0808: 目标IP=8.8.8.8
    - d431: 源端口=54321 (随机临时端口) ← UDP源端口
    - 0035: 目标端口=53 (DNS)              ← UDP目的端口
    - 0028: UDP长度=40 (8字节头+32字节数据) ← UDP长度
    - ea9f: 校验和                          ← UDP校验和
    - 后面是 DNS 查询数据: www.baidu.com 的查询...
# ============================================
# 示例 2: 查看/调整 UDP 缓冲区参数
# ============================================

# 查看 UDP 接收缓冲区最大值(单位: 字节)
sysctl net.core.rmem_max
# 默认值通常: net.core.rmem_max = 212992 (约 208KB)

# 查看 UDP 发送缓冲区最大值
sysctl net.core.wmem_max
# 默认值通常: net.core.wmem_max = 212992 (约 208KB)

# 如果你的应用需要处理高速 UDP 流量(如视频直播、KCP 传输),
# 可能需要调大缓冲区防止丢包:
sudo sysctl -w net.core.rmem_max=26214400   # 接收缓冲区改为 25MB
sudo sysctl -w net.core.wmem_max=26214400   # 发送缓冲区改为 25MB

# 永久生效: 写入 /etc/sysctl.conf
# echo "net.core.rmem_max=26214400" | sudo tee -a /etc/sysctl.conf
# ============================================
# 示例 3: 用 nc (netcat) 快速测试 UDP 通信
# ============================================

# 终端 A: 启动一个 UDP 监听服务
nc -u -l -p 9999

# 终端 B: 向该端口发送 UDP 数据
echo "Hello UDP!" | nc -u 127.0.0.1 9999

# 终端 A 会显示: Hello UDP!
# 注意: 如果终端 A 没启动就发数据, 数据会静默丢弃 (这就是"不可靠")

重要提醒:UDP 的”不可靠”是双刃剑

优点:

  • 速度快:无握手延迟,无等待确认的时间开销
  • 简单:实现简单,调试方便
  • 灵活:支持广播和多播
  • 高效:小包场景下头部开销占比极低

缺点:

  • 可能丢包:网络拥堵时包会被路由器直接丢弃
  • 可能乱序:不同路径到达顺序可能不同
  • 可能重复:底层网络可能会复制包
  • 无流控:发送太快会把接收方淹没

思考题:如果我想用 UDP 的速度优势,但又需要数据的可靠性怎么办?

, 这正是下一章要解决的问题:在 UDP 之上构建可靠的传输层。


让 UDP 变可靠:主流方案全景图

核心矛盾:为什么要给 UDP "穿铠甲"?

上一章我们说 UDP 天生不可靠,不确认、不重传、不排序、不流控。但现实世界中,很多场景既需要低延迟,又需要数据可靠性。

┌─────────────────────────────────────────────────────┐
│                                                     │
│   TCP: ✅ 可靠  ✅ 有序  ✅ 流控                     │
│        ❌ 延迟高(握手+重传)                          │
│        ❌ 队头阻塞                                   │
│        ❌ 拥塞控制不可定制                            │
│                                                     │
│   UDP: ✅ 低延迟  ✅ 简单  ✅ 支持广播                 │
│        ❌ 不可靠  ❌ 无序  ❌ 无流控                   │
│                                                     │
│   ─────────────────────────────────                  │
│   能不能兼顾: 低延迟 + 可靠性 + 安全性?               │
│   → 在 UDP 上叠加自定义的可靠性机制!                 │
│                                                     │
└─────────────────────────────────────────────────────┘

TCP 的固有局限驱动了替代方案的出现:

TCP 的痛点具体表现谁在意?
拥塞控制不可修改内核协议栈硬编码了 Cubic/BBR 等算法,应用层难以自定义游戏开发者、音视频工程师
队头阻塞(HOL Blocking)一个包丢失会阻塞后续所有包的交付HTTP/2 用户(尤其移动网络)
三次握手开销短连接场景下握手占比极高API 调用、频繁请求
连接由四元组标识WiFi ↔ 4G 切换导致连接断开移动端用户
TIME_WAIT 等待高并发短连接耗尽端口资源服务器运维

这些痛点催生了多种”UDP + 自定义可靠性 + 安全性”的方案。下面我们逐一介绍。

六大方案总览对比

维度RUDPUDTKCPQUICenetRakNet
核心定位教学/基础实现大数据高速传输低延迟实时传输Web 下一代标准游戏轻量库游戏完整框架
诞生时间~1980s (概念)200420132013 (Google) / 2021 (IETF)~20052000s
可靠性机制停等 ARQ / 回退 NSACK + 可调窗口ARQ + SACK + 快速重传ACK + SACK + FEC可靠/不可靠混合通道排序 ACK 系统
拥塞控制无自定义 CC 算法可配置,甚至可关闭BBR/Cubic/NewReno 可插拔无内置有
安全性无可选加密(非内置) 需上层叠加加密 TLS 1.3 内置(强制)可选 AES 加密模块RakNetSSL 插件
标准化程度无 RFC(教学概念)RFC 相关草案 无 RFC(GitHub 开源项目) RFC 9000 / 9001 / 9002无(开源 C 库)无(商业/开源混合)
多路复用单流单流单流Stream 多路复用(无 HOL)通道(Channel) 机制可靠/不可靠通道分离
连接迁移不支持不支持不支持 支持(Connection ID)不支持不支持
握手延迟0(无握手)~1 RTT≈ 0(无需握手)1 RTT(首次)/ 0 RTT(复用)≈ 0~1 RTT
典型场景学习 ARQ 原理科学计算/大数据同步音视频通话/在线游戏/VPNHTTPS/Web API/MOBA/FPSUnity 多人游戏
成熟度/生态教学级中(科研领域)中高(国内广泛使用)高(HTTP/3 标准)低(维护停滞)中(Unity 收购后)

关于”标准化程度”的理解:

  • RFC 标准(如 QUIC):意味着跨厂商互操作性,Chrome 和 Nginx 可以无缝通信,因为双方都实现了同一个 RFC 规范。
  • 开源项目(如 KCP/enet):意味着你需要在客户端和服务端都集成同一套 SDK,没有”标准互操作”的概念。
  • 商业方案(如 RakNet):可能需要付费授权,但提供完整的技术支持和工具链。

各方案简要说明

RUDP(Reliable UDP) 最基础的”可靠 UDP”实现思路,在 UDP 之上手动加上序列号、确认应答和超时重传。

RUDP 工作流程:
发送方: [数据] + [SeqNum=1] → UDP → 网络
接收方: 收到 Seq=1, 回复 ACK=2
发送方: 收到 ACK=2, 发送 Seq=2 ...
如果超时没收到 ACK → 重传!

特点:
- 实现简单, 适合学习 ARQ (自动重传请求) 原理
- 通常使用停等协议 (Stop-and-Wait) 或回退 N (Go-Back-N)
- 生产环境很少直接使用 (太简陋)

UDT(UDP-based Data Transfer)

专为大数据高速传输设计的协议,常用于地理信息科学、基因数据分析等需要传输海量数据的领域。

UDT 特点:
- 基于 UDP, 但实现了类似 TCP 的可靠性和拥塞控制
- 拥塞控制算法针对高速广域网优化 (不同于 TCP 的 Cubic)
- 支持海量数据传输 (TB 级别)
- 典型应用: 科学计算网格、卫星数据回传

性能对比 (在高带宽高延迟链路上):
TCP 吞吐: ~50 Mbps (受 BDP 限制)
UDT 吞吐: ~900 Mbps (接近线路容量)
→ UDT 在长肥管道 (Long Fat Pipe) 场景下远优于 TCP

KCP(本文重点之一,下文会详解)

由中国开发者 skywind3000 于 2013 年创建的开源项目,专注于低延迟可靠传输。国内广泛应用于游戏加速器、实时音视频、VPN 等领域。

QUIC(本文重点之二,下文会详解)

由 Google 提出,经 IETF 标准化后的下一代传输协议,已成为 HTTP/3(RFC 9114)的底层传输层。Chrome/Firefox/Edge/Safari 已全面支持。

enet

一款轻量级的C 语言网络库,专门为实时多人游戏设计,提供了可靠的有序传输和不可靠的无序传输两种模式。

RakNet

功能完整的游戏网络引擎,曾用于《 Minecraft 》主机版、《 消逝的光芒》等知名游戏。2021 年被 Unity 收购后以开源形式发布(GPLv2 协议)。

选型决策树

面对这么多方案,实际项目中该怎么选?

                    你的核心需求是什么?
                           │
         ┌─────────────────┼─────────────────┐
         ▼                 ▼                  ▼
   需要 Web 浏览器     追求极致低延迟       大数据传输
   /HTTPS 兼容?       (实时交互)?          (GB/TB级别)?
         │                 │                  │
         ▼                 ▼                  ▼
    ┌───┴───┐      ┌──────┴──────┐    ┌──────┴──────┐
    │ 能否集 │      │ 只做游戏吗? │    │ 是否需要标  │
    │成SDK? │      │             │    │准化互操作? │
    └───┬───┘      └──────┬──────┘    └──────┬──────┘
   是 ↓   否 ↓     是 ↓    否 ↓      是 ↓    否 ↓
    │      │        │       │         │       │
    KCP   QUIC    enet/   KCP      UDT    自定义/
  (国内用) (HTTP/3) RakNet  (通用)  (科学计算) RUDP

一句话速查表:

如果你是……推荐方案原因
Web 开发者(API/网页服务)QUIC (HTTP/3)浏览器原生支持,零集成成本
游戏开发者(MOBA/FPS)KCP 或 enet/RakNet极致低延迟,可控性强
音视频工程师(VoIP/直播)KCP延迟敏感 + 可自建安全层
VPN/加速器开发者KCP抗丢包能力强,可关闭拥塞控制
科学计算/大数据工程师UTD高速广域网传输优化
学生/学习者RUDP最简 ARQ 实现,适合理解原理

KCP:为低延迟而生的可靠传输协议

重要声明:KCP 没有官方 RFC 规范。

它是由中国开发者 skywind3000 于 2013 年创建的开源项目(C 语言实现,约 5000 行代码),代码托管于:

https://github.com/skywind3000/kcp

本文技术细节以作者 wiki 和源码为准。KCP 在国内游戏、音视频、VPN 领域有广泛使用,但请注意其非标准协议的性质,这意味着你需要客户端和服务端都集成同一套 KCP 库。

设计哲学:以延迟换可靠性

理解 KCP 的核心在于理解它和 TCP 的设计目标差异:

┌──────────────────────┬──────────────────────────┬──────────────────────┐
│                      │          TCP             │        KCP           │
├──────────────────────┼──────────────────────────┼──────────────────────┤
│ 设计优先级            │ 公平性 > 吞吐量 > 延迟    │ 延迟 > 吞吐量 > 公平性 │
│ 核心目标              │ 最大化带宽利用率           │ 最小化传输延迟        │
│ 重传时机              │ 等超时(RTO) 或 3个重复ACK │ **不等超时,主动快速重传**│
│ ACK 策略             │ 延迟 ACK (合并确认)        │ **立即 ACK (可配置)**   │
│ Nagle 算法           │ 默认开启(合并小包)         │ **不使用 Nagle**       │
│ 拥塞控制             │ 内核强制,不可关闭         │ **可配置,甚至可完全关闭**│
│ 适用场景              │ 文件下载 / 网页浏览         │ 游戏 / 音视频 / 实时通信 │
└──────────────────────┴──────────────────────────┴──────────────────────┘

一句话总结:
TCP: "我要公平地使用网络,宁可慢一点也不能抢别人的带宽"  
KCP: "我要最快地送达数据,可以牺牲一点公平性换取低延迟"

协议内部架构:缓冲区模型

KCP 的数据流转依赖于一套精心设计的缓冲区系统:

  发送端 (Sender)                              接收端 (Receiver)

 ┌─────────────┐     ┌─────────────┐     ┌──────────────────┐
 │ 应用层调用   │     │ send_q      │     │                  │
 │ikcp_send()  │ ──→ │ (发送队列)   │     │                  │
 └─────────────┘     └──────┬──────┘     │                  │
                            │ flush()时   │                  │
                            ▼             │                  │
                     ┌─────────────┐     │    ┌─────────────┐
                     │ snd_queue   │     │    │ rcv_queue   │
                     │ (待发送队列) │─────┼UDP─┼→│ (接收队列)  │
                     └──────┬──────┘     │    │             │
                            │            │    └──────┬──────┘
                            ▼ UDP发送    │           │ ikcp_recv()
                     ┌─────────────┐     │           ▼
                     │ snd_buf     │     │    ┌─────────────┐
                     │ (发送缓冲区) │◀────┼────│ ackbuf_list │
                     │ (已发待确认) │ ACK  │    │ (收到ACK列表)│
                     └─────────────┘     │    └─────────────┘
                            │            │           │
                            │            │           ▼ 回复ACK
                       ┌────┴────┐       │    ┌─────────────┐
                       │ unack_   │       │    │ rcv_buf     │
                       │ list     │       │    │ (接收缓冲区)  │
                       │ (已发未确认│      │    │ (乱序重组)   │
                       │  的包)    │       │    └─────────────┘
                       └──────────┘       │
                                          │
数据流向说明:
① ikcp_send(data): 用户数据 → send_q
② ikcp_flush(): send_q → snd_queue → 封装KCP头 → UDP发出 → 进入snd_buf等待确认
③ 收到对方ACK: 从snd_buf移除已确认的包
④ 接收方收到: 数据进入rcv_buf(可能乱序) → 排序后入rcv_queue
⑤ ikcp_recv(): 从rcv_queue取数据交给应用层
⑥ 接收方同时生成ACK回传发送方

关键理解:send_q 和 snd_queue 是一回事吗?

不是。send_q 是用户数据的”蓄水池”,flush() 时才从中取数据封装成 KCP 包放入 snd_queue。这种设计允许应用层在任何时间调用 ikcp_send() 而不需要关心底层是否正在发包,类似 TCP 的写缓冲区概念。

核心机制一:ARQ 快速重传

三种确认模式 KCP 提供三种不同粒度的确认机制:

模式全称工作方式开销适用场景
UNAUnacknowledged只确认最后一个连续收到的序号最小(1个序号)无丢包的理想环境
ACKAcknowledge独立确认每个收到的包中等(每包1个ACK)一般场景
SACKSelective ACK选择性确认 + 标记丢失范围较大(携带丢包信息)高丢包环境
SACK 工作示意:

发送方发送: [1] [2] [3] [4] [5]
接收方收到: [1]✅ [2]✅ [3]❌丢了! [4]✅ [5]✅

传统 ACK: 只能说 "我收到了1和2, 下一个请发3"
→ 发送方不知道4和5也收到了, 可能重传[3][4][5]

KCP SACK: 可以说 "我收到了1~2, 4~5, 3丢了"
→ 发送方只需重传 [3] ✅ 效率高!

快速重传策略:不等超时的秘密武器

这是 KCP 与 TCP 最大的区别之一。

时间轴 →

TCP 的重传行为:
|---发送pkt1--|--发送pkt2--|--发送pkt3✗--|..................等RTO(200ms+)..............|
                                                                  |--重传pkt3--|
↑ TCP 必须等到超时(RTO)或收到3个重复ACK才能重传

KCP 的快速重传 (interval模式):
|---发送pkt1--|--发送pkt2--|--发送pkt3✗--|-10%-重传-|-20%-重传-|(收到ACK停止)-|
↑ 每过 interval 比例的时间就主动重传一次!
↑ 即使 RTO 还没到!

关键参数:
- interval: 内部工作频率 (默认10ms, 即每秒更新100次)
- resend: 每个 RTT 内的快速重传次数 (默认2次)
- nodelay: 是否启用无延迟模式

实际效果:
TCP 丢包恢复: 通常需要 200ms ~ 2s (取决于RTO)
KCP 丢包恢复: 通常 < 30ms (快速重传)
→ 快 7 倍以上!

核心机制二:nodelay 模式的四大参数

KCP 提供了一个强大的配置接口 ikcp_nodelay(),这是调优的核心:

// 函数原型 (官方 API)
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc);
参数类型含义取值范围推荐值说明
nodelayint是否启用无延迟模式0 或 110=正常模式(类似TCP),1=取消延迟ACK和Nagle
intervalint内部工作时钟频率(毫秒)1 ~ 500010每 N ms 调用一次 ikcp_update();KCP 库默认约 100ms,低延迟场景建议设为 10ms(即每秒100次)
resendint快速重传次数(每个 RTT 内)0 ~ ~1020=仅超时重传(退化为类TCP),2=激进快速重传
nc (no congestion)int是否关闭拥塞控制0 或 1看场景0=保留拥塞控制,1=完全关闭(杀手特性)
// ===== 典型配置示例 =====

// 场景1: 极速模式 — 追求最低延迟(牺牲公平性)
// 适用: 专用链路 / 内网游戏 / VPN 加速器
ikcp_nodelay(kcp, 1, 10, 2, 1);
// 解释:
// nodelay=1   → 不延迟ACK, 不做Nagle合并
// interval=10 → 10ms刷新一次 (高灵敏度)
// resend=2    → 每RTT内快速重传2次 (激进)
// nc=1        → 关闭拥塞控制! 全速前进!

// 场景2: 平衡模式 — 速度与公平性的折中
// 适用: 公网音视频通话 / 跨网传输
ikcp_nodelay(kcp, 1, 20, 2, 0);
// 解释:
// interval=20 → 20ms刷新 (降低CPU占用)
// nc=0        → 保留拥塞控制 (不做网络破坏者)

// 场景3: 保守模式 — 类似TCP行为
// 适用: 不确定网络状况时的默认值
ikcp_nodelay(kcp, 0, 50, 0, 0);
// 解释:
// nodelay=0   → 使用延迟ACK (减少ACK数量)
// interval=50 → 50ms刷新 (低CPU)
// resend=0    → 不做快速重传 (仅超时重传)
// nc=0        → 保留拥塞控制

核心机制三:流量控制 & 拥塞控制

流量控制 KCP 的流量控制基于滑动窗口机制,与 TCP 类似但更灵活:

发送窗口 = min(对端通告的接收窗口(rwnd), 自身拥塞窗口(cwnd))

可通过以下 API 配置:
ikcp_wndsize(kcp, snd_wnd, rcv_wnd);
// snd_wnd: 发送窗口大小 (数据包个数, 不是字节数!)
// rcv_wnd: 接收窗口大小 (同上)

典型设置:
ikcp_wndsize(kcp, 256, 256);  // 各256个包的窗口
// 每个包最大约 1400字节 (MTU - IP头 - UDP头 - KCP头)
// → 窗口容量 ≈ 256 × 1400 ≈ 358KB

拥塞控制,可关闭的杀手级特性

这是 KCP 区别于 TCP 的最关键设计决策:

TCP 的拥塞控制 (强制开启, 无法关闭):
┌─────────────────────────────────────┐
│  cwnd (拥塞窗口)                     │
│                                      │
│  64|               *                │
│    |             *   *               │ ← 慢启动 (指数增长)
│ 32|           *       *              │
│    |         *           *            │
│ 16|-------*---------------*-----------│← ssthresh (阈值)
│    |     *                   *       │ ← 拥塞避免 (线性增长)
│  8|   *                         *    │
│    | *                             *  │
│  1|*                                 │
│    +--------------------------------→ 时间
│      发现丢包? cwnd 直接减半!        │
│      → 吞吐量骤降                     │
└─────────────────────────────────────┘

KCP 的拥塞控制 (可选! 可关闭!):
┌─────────────────────────────────────┐
│                                      │
│  当 nc=1 时:                         │
│                                      │
│  cwnd                                │
│  ∞ |********************************│ ← 全速发送!
│    |********************************│   不管网络拥不拥塞!
│    |********************************│
│  0 +--------------------------------→ 时间
│                                      │
│  代价: 可能抢占其他流的带宽            │
│  收益: 极致的吞吐量和低延迟            │
│                                      │
│  适用场景:                            │
│  - 专用链路 (专线/内网)               │
│  - 愿意付费买更好带宽的场景             │
│  - 对实时性要求高于公平性的场景         │
└─────────────────────────────────────┘

关于关闭拥塞控制的道德提醒:

在公共互联网上关闭拥塞控制会导致你的流量”抢占”其他 TCP 流的带宽,这对其他用户不公平,甚至可能被运营商检测到并限速/封禁。

合理的做法是:在可控的网络环境中使用极速模式(如自建专线、企业内网),或在公网上使用平衡模式(nc=0)。

安全性:KCP 的安全层

KCP 原生不包含任何加密或认证机制!

这是一个非常重要的认知,很多人误以为”用了 KCP 就安全了”,其实不然。

KCP 本质上只解决"可靠传输",不解决"安全传输"

❌ 原始 KCP 的问题:

明文数据流:
[应用数据] → [KCP封装(序列号+校验)] → [UDP] → 网络
                                              ↑
                                    任何人都能看到你的数据!
                                    任何人都能伪造KCP包!
                                    中间人可以篡改/注入/丢弃!

风险:
1. 窃听: 数据内容完全暴露 (如聊天记录、游戏指令)
2. 篡改: 攻击者修改数据包 (如修改游戏中的位置坐标)
3. 注入: 攻击者发送虚假包 (如伪造断开连接指令)
4. 重放: 攻击者捕获旧包重新发送 (如重复扣款操作)

解决方案:上层叠加加密层

✅ 正确的部署方式:

┌─────────────────────────────────────────────┐
│              应用层 (Application)             │
├─────────────────┬───────────────────────────┤
│   AES-256-GCM   │   salsa20/poly1305        │  ← 加密层 (你来实现!)
│   ChaCha20-Poly │   自定义加密方案            │
│   或 DTLS 1.2   │                           │
├─────────────────┴───────────────────────────┤
│              KCP (可靠传输层)                 │  ← 只负责可靠性
├─────────────────────────────────────────────┤
│              UDP (传输层)                    │
└─────────────────────────────────────────────┘

常见加密组合方案:

组合方式使用场景优点缺点
KCP + AES-256-CBC/GCM国内 VPN/加速器主流成熟广泛,性能好需要 PKI 密钥管理
KCP + salsa20/poly1305某些在线游戏轻量,适合嵌入式生态较小
KCP + ChaCha20-Poly1305移动端优化选择ARM 上性能优于 AES较新
KCP + DTLS需要标准化的场景有证书体系支持开销较大,复杂度高
KCP + 自定义对称加密完全自主可控灵活度最高自己负责安全性审计

实战建议:如果你在使用 KCP 做产品,一定要在 KCP 层之下、UDP 层之上加一个加密层(或者 KCP 层之上也可以)。推荐使用成熟的加密库如 OpenSSL、libsodium 或 BoringSSL,不要自己实现加密算法。

FEC 前向纠错(可选扩展模块)

KCP 社区有 FEC(Forward Error Correction)扩展实现:

FEC 原理:

普通模式 (无FEC):
发送: [数据包1] [数据包2] [数据包3] [数据包4]
接收: [包1✅] [包2✅] [包3❌丢失!] [包4✅]
→ 需要请求重传包3 (增加一个 RTT 的延迟!)

FEC 模式:
发送: [数据包1] [数据包2] [数据包3] [数据包4] [冗余包R1]
       (4个数据包 + 1个冗余包, 冗余率 25%)
接收: [包1✅] [包2✅] [包3❌丢失!] [包4✅] [R1✅]
→ 用 R1 通过数学运算恢复出包3! 无需重传! 延迟为0!

算法基础: Reed-Solomon 编码 (任意 N 个包中收到 M 个即可恢复全部)

代价:
- 增加带宽开销 (冗余率越高, 开销越大)
- 增加编码/解码的计算开销
- 适用于: 高丢包率且重传代价大的场景 (卫星链路、弱网环境)

注意:FEC 不是 KCP 核心库的一部分,而是社区扩展。GitHub 上搜索 “kcp fec” 可以找到多个开源实现。

性能基准数据

根据 KCP 作者 skywind3000 提供的测试数据:

性能指标TCPKCP (nodelay 模式)提升
平均传输延迟100%(基线)~65%↓ 降低 35%
最大传输延迟100%(基线)~10%↓ 降低 90%
丢包恢复时间200ms+(RTO)< 30ms(快速重传)快 7x+
单连接吞吐量受限于拥塞控制可超过 TCP(关拥控时)看场景
延迟对比示意图:

TCP 延迟曲线:
|  _
| | \_______
| |         \________ 平均延迟较高
| |
| +--------------------→ 时间
|  ↑抖动大, 最大延迟可达数百毫秒

KCP 延迟曲线:
|  _  _  _
| |  |||
| |  |||______ 平均延迟低且稳定
| |
| +--------------------→ 时间
|  ↑抖动小, 最大延迟被严格控制

客观提醒:以上为理想网络条件下的相对数据。实际效果因网络状况(丢包率、延迟、带宽)、KCP 参数配置、以及具体实现而异。建议在自己的真实环境中进行 benchmark 测试。

完整工作流程图

完整的 KCP 数据传输流程:

  发送端(Sender)                          接收端(Receiver)
       |                                       |
       | [1] ikcp_send("Hello World")          |
       |   → 数据存入 send_q                    |
       |                                       |
       | [2] ikcp_update() 定时触发 flush       |
       |   → send_q → snd_queue                 |
       |   → 封装 KCP 头部:                     |
       |     conv=连接标识                      |
       |     cmd=IKCP_CMD_PUSH (数据包)         |
       |     frg=分片编号                        |
       |     wnd=可用窗口大小                    |
       |     ts=时间戳                           |
       |     sn=序列号 (递增)                    |
       |     una=期望收到的第一个未确认序号      |
       |     len=数据长度                        |
       |     data="Hello World"                  |
       |                                       |
       | [3] 通过 UDP 发送出去                  |
       | ======================================= UDP 网络 (可能丢包!) === |
       |                                       |
       | [4] 接收到 KCP 包                      |
       |   → 校验数据完整性                     |
       |   → 根据 sn 判断是否有序               |
       |   → 有序: 直接入 rcv_queue              |
       |   → 乱序: 存入 rcv_buf 等待后续排序      |
       |   → 生成 ACK (包含 una + 最新 sn)       |
       |                                       |
       | [5] 回复 ACK 包 (通过 UDP)              |
       | ======================================= UDP 网络 ====================== |
       |                                       |
       | [6] 发送端收到 ACK                     |
       |   → 更新 snd_buf (移除已确认的包)        |
       |   → 更新发送窗口                        |
       |   → 如果发现有丢包 (通过 UNA/SACK 判断)  |
       |     → 立即触发快速重传! (不等RTO)        |
       |                                       |
       | [7] 接收端应用层调用 ikcp_recv()         |
       |   → 从 rcv_queue 按序取出完整数据        |
       |   → 返回 "Hello World" 给应用           |
|                                       |
       └──────────── 循环往复 ────────────┘

特殊情况处理:
[A] 丢包重传:
    发了 [sn=5], 但没收到它的 ACK
    → 下次 update() 时检测到
    → 立即重传 [sn=5] (如果 resend > 0)
    → 即使 RTO 还没到!

[B] 乱序到达:
    收到了 [sn=7], 但 [sn=6] 还没来
    → 把 [sn=7] 暂存在 rcv_buf
    → 等 [sn=6] 到达后一起按序交付给应用

[C] 重复包:
    收到了 [sn=5], 但之前已经确认过了
    → 丢弃重复包 (不交付给应用)
    → 重传 ACK (防止对方不知道已经收到)

QUIC:Google 革新 → IETF 标准 → HTTP/3 的基石

术语说明:本文用户原始需求中提到的 “QUICK”,统一指代 QUIC(Quick UDP Internet Connections,RFC 9000)。这是 Google 提出、IETF 标准化的下一代传输协议,以下均使用标准名称 QUIC。

从 Google QUIC 到 IETF QUIC:发展时间线

QUIC 发展历程:

2012   Google 内部开始实验: "如果用 UDP + 自定义协议替代 TCP+TLS 会怎样?"
       │
2013   Google 推出 gQUIC (基于 SPDY, Chrome 可启用)
       │  - 目标: 减少 HTTPS 页面加载延迟
       │  - 在 UDP 上实现了可靠加密传输
       │
2015   gQUIC 流量占 Google 总流量约 7%
       │
2016   IETF 成立 QUIC 工作组, 开始标准化
       │  - 决定不兼容 gQUIC, 重新设计 (称为 iQUIC/IETF QUIC)
       │  - TLS 1.3 作为唯一握手协议 (gQUIC 用的是自定义类似TLS的协议)
       │
2018   IETF QUIC 草案陆续发布, 多个实现出现:
       │  - quiche (Cloudflare, Rust)
       │  - ngtcp2 (C/C++)
       │  - aioquic (Python)
       │  - MsQuic (Microsoft, C)
       │
2021   ★ RFC 9000 / 9001 / 9002 正式发布!
       │  QUIC 成为 IETF 国际标准
       │
2022   HTTP/3 (RFC 9110 / 9114) 成为正式标准
       │  QUIC 成为 HTTP/3 的底层传输协议
       │
2024   主流浏览器全面支持:
       │  Chrome / Edge (Chromium) ✅
       │  Firefox ✅
       │  Safari (iOS 17+/macOS 14+) ✅
       └── 当前状态: 全球超过 30% 的 Web 流量使用 QUIC/HTTP/3

QUIC vs TCP + TLS:握手速度对比

这是 QUIC 最直观的优势,大幅减少连接建立的 RTT 次数。

┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│  TCP + TLS 1.2 握手过程 (~3.5 RTT):                                   │
│                                                                     │
│  Client                                          Server             │
│    |                                                 |               │
│    |------- SYN ----------------------------------->| ①              │
│    |<------ SYN + ACK ------------------------------| ②              │
│    |------- ACK ----------------------------------->| ③              │
│    |        [TCP 连接建立完成: 已过 1 RTT]            |                │
│    |                                                 |               │
│    |------- ClientHello (TLS 握手) ---------------->| ④              │
│    |<------ ServerHello + Certificate -------------| ⑤              │
│    |------- KeyExchange + ChangeCipherSpec -------->| ⑥              │
│    |<------ NewSessionTicket + Finished -----------| ⑦              │
│    |------- Finished ----------------------------->| ⑧              │
│    |        [TLS 握手完成: ~2 RTT]                    |                │
│    |                                                 │               │
│    | ========= 开始传输应用数据 ==========           │                │
│    |------- GET https://example.com -------------->|                 │
│    |<------ 200 OK (HTML) ------------------------|                 │
│    |                                                 │               │
│  总计首字节时间: 1.5 RTT (TCP) + ~2 RTT (TLS) ≈ 3.5 RTT             │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│  QUIC (IETF) 1-RTT 握手过程 (首次连接):                             │
│                                                                     │
│  Client                                          Server             │
│    |                                                 |               │
│    |------- Initial 包 (Client Hello) ------------>|                 │
│    |        包含: TLS ClientHello + QUIC 参数         │                │
│    |<------ Initial + Handshake --------------------|                 │
│    |        包含: TLS ServerHello + Certificate      │                │
│    |              + Finished (加密握手完成)          │                │
│    |------- Handshake (Finished) ----------------->|                 │
│    |<====== 1-RTT 加密密钥已建立 ========           │                │
│    |<------ 1-RTT 包 (加密的应用数据响应!) ----------|                 │
│    |                                                 │               │
│    | ========= 开始传输数据 (仅 1 RTT!) ==========    │                │
│    |------- STREAM 数据 (如 HTTP GET) ------------> |                 │
│    |<------ STREAM 响应 (加密的 HTML) --------------|                 │
│    |                                                 │               │
│  总计首字节时间: 1 RTT (!)                                              │
│  比 TCP+TLS 1.2 快了 3.5倍!                                            │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│  QUIC 0-RTT 握手 (恢复之前的连接):                                  │
│                                                                     │
│  Client                                          Server             │
│    |                                                 |               │
│    |------- 0-RTT 包 (应用数据提前发送!) ---------->|                 │
│    |        包含: 之前缓存的参数                    │                │
│    |              + 应用层请求 (如 HTTP GET)        │                │
│    |        ⚠️ 注意: 此时还没完成握手验证!           │                │
│    |<------ 1-RTT 包 (响应 + 新的 Session Ticket) --|                 │
│    |                                                 │               │
│    | ========= 0 RTT 即开始传数据! ===========        │                │
│    |                                                 │               │
│  总计首字节时间: ≈ 0 RTT (接近即时!)                                   │
│  条件: 客户端和服务端之前有过成功连接                              │
└─────────────────────────────────────────────────────────────────────┘

总结对比:

┌─────────────────────┬──────────────┬────────────────┐
│ 协议组合            │ 握手所需 RTT │ 首字节时间     │
├─────────────────────┼──────────────┼────────────────┤
│ TCP + TLS 1.2      │ ~3.5 RTT     │ ~3.5~4 往返         │
│ TCP + TLS 1.3      │ ~2.5 RTT     │ ~2.5~3 往返         │
│ QUIC (首次连接)     │ **1 RTT**     │ **~1 往返**          │
│ QUIC (恢复连接)     │ **0 RTT**     | **≈ 0 (客户端发送)** ⚠️ │
└─────────────────────┴──────────────┴────────────────┘

RTT 拆解说明(依据 RFC 793 / RFC 8446 / RFC 9000):

  • TCP 三次握手 = 1.5 RTT(SYN → SYN+ACK → ACK)
  • TLS 1.2 完整握手 = 2 RTT → TCP + TLS 1.2 合计 ~3.5 RTT
  • TLS 1.3 完整握手 = 1 RTT → TCP + TLS 1.3 合计 ~2.5 RTT
  • QUIC 首次连接:握手与加密合并,服务端可在首个响应包内直接返回数据 → ~1 RTT
  • QUIC 恢复连接:客户端可立即发送请求(0-RTT),但服务端响应仍需约 1 RTT

核心特性一:TLS 1.3 内置加密(强制,不可关闭!)

QUIC 与 TCP + TLS 最本质的区别在于安全模型的设计位置不同:

┌─────────────────────────────┐    ┌─────────────────────────────┐
│     TCP + TLS 架构          │    │      QUIC 架构              │
├─────────────────────────────┤    ├─────────────────────────────┤
│                             │    │                             │
│  ┌───────────┐  ┌────────┐ │    │  ┌─────────────────────┐   │
│  │ 应用层数据  │→│ TLS层  │ │    │  │  应用层数据          │   │
│  │ (HTTP等)  │  │(可选!) │ │    │  └────────┬────────────┘   │
│  └───────────┘  └───┬────┘ │    │           │ 加密            │
│                     │      │    │  ┌────────▼────────────┐   │
│  ┌──────────────────▼──┐  │    │  │   QUIC 层 (内置加密)  │   │
│  │      TCP 层         │  │    │  │   TLS 1.3 是协议的一部分│   │
│  │  (明文头部!)         │  │    │  │   无法关闭!           │   │
│  └──────────┬───────────┘  │    │  └────────┬────────────┘   │
│             │             │    │           │ 封装到UDP包      │
│  ┌──────────▼───────────┐  │    │  ┌───────▼────────────┐   │
│  │      IP 层            │  │    │  │   UDP 层            │   │
│  └──────────────────────┘  │    │  └─────────────────────┘   │
└─────────────────────────────┘    └─────────────────────────────┘

关键区别:

TCP + TLS:
- 加密是"外挂"的, 应用层决定是否使用
- TCP 头部 (序列号、ACK 等) 是明文的! ← 中间人可分析流量特征
- 可以不用 TLS (很多旧系统如此)

QUIC:
- 加密是"内置"的, 协议层面强制要求
- 几乎所有头部字段都加密! ← 端到端加密
- 只有 Connection ID 和部分包类型标识是明文
- 不存在"未加密的 QUIC"

QUIC 的加密范围

QUIC 包结构 (Long Header 示例):

明文部分 (所有网络设备可见):
┌──────┬──────┬──────┬──────────────────────┐
│Header│ Packet│ Vers │ Destination CID      │ ← 路由需要这些信息
│ Form │ Type  │ ion  │ (Connection ID, 不可预测)│
└──────┴──────┴──────┴──────────────────────┘

加密部分 (只有端点能解密):
┌──────────────────────────────────────────────┐
│ Source CID    │ Length  │ Packet Number       │ ← 大部分已加密
│ Token (可选)  │        │ Payload (实际数据)    │
└──────────────────────────────────────────────┘
              ↑
         AEAD 加密 (AES-GCM 或 ChaCha20-Poly1305)

结果:
- 路由器只能看到: 这是一个 QUIC 包, 发给哪个 CID
- 路由器无法看到: 里面是什么数据, 发了多少个流, 序列号是什么
→ 有效防止深度包检测 (DPI) 和流量指纹识别

0-RTT 的安全隐患:重放攻击

0-RTT 允许客户端在握手完成前就发送数据,但这带来了一个风险:攻击者可以捕获并重放 0-RTT 数据。

攻击场景:
1. 用户向银行发送 0-RTT 请求: "转账100元给Alice"
2. 攻击者捕获了这个 0-RTT 包
3. 攻击者重发这个包
4. 银行服务器再次执行转账! → Alice 收到了双倍的钱 

缓解措施 (RFC 9001 定义):

  • 服务端只允许幂等操作使用 0-RTT(如 HTTP GET)
  • 非 0-RTT 操作(POST/PUT)必须等待 1-RTT 握手完成
  • 使用单次使用 Token(服务端发放的临时凭证,只能用一次)
  • 缓存已处理的 0-RTT 数据去重

结论:0-RTT 很快但不是万能的,对于敏感操作仍需等待完整握手。

核心特性二:Stream 多路复用无队头阻塞

这是 QUIC 解决的另一个 TCP 固有痛点:

问题: HTTP/2 over TCP 的队头阻塞 (Head-of-Line Blocking)

TCP 连接 (1个):
┌──────────────────────────────────────────────────────┐
│                                                      │
│ Stream 1: [GET /page.html] ........... [*丢包!*] ❌  │
│ Stream 2: [GET /style.css] ✅ 已到达! 但要等Stream1! │ ← 被阻塞!
│ Stream 3: [GET /image.png] ✅ 也到达了! 还是要等!    │ ← 全堵了!
│                                                      │
│ 原因: TCP 是字节流, 不区分逻辑边界                   │
│       一个包丢失 → 后续所有数据都无法交给应用层        │
│       (因为接收方无法确定后续字节的逻辑边界)           │
│                                                      │
└──────────────────────────────────────────────────────┘

QUIC 的解决方案: Stream 独立交付

QUIC 连接 (1个):
┌──────────────────────────────────────────────────────┐
│                                                      │
│ Stream 1: [GET /page.html] ...... [*丢包!*] ❌       │
│ Stream 2: [GET /style.css] ✅ → 立即交付给应用!       │ ← 不受影响!
│ Stream 3: [GET /image.png] ✅ → 也立即交付!          │ ← 继续正常! │
│                                                      │
│ 原因: 每个 Stream 有独立的序列号和 ACK 机制           │
│       Stream 1 丢了包只影响 Stream 1                  │
│       Stream 2/3 完全独立, 各走各的                   │
│                                                      │
│ 类比:                                               │
│ TCP 就像一条单车道公路 — 一辆车抛锚, 所有车都过不去   │
│ QUIC 就像多车道高速公路 — 一条道出事, 其他道照常通行   │
│                                                      │
└──────────────────────────────────────────────────────┘

实测效果:在移动弱网环境下(丢包率 1%~2%),HTTP/2 over TCP 的页面加载时间可能比 HTTP/1.1 更慢(因为 HOL 阻塞放大了影响),而 QUIC (HTTP/3) 则始终稳定优于两者。

核心特性三:Connection ID 连接迁移

这是 QUIC 最具创新性的特性之一:

传统 TCP 连接的身份标识:
┌─────────────────────────────────────────────────┐
│ TCP 连接 = {源IP, 源端口, 目的IP, 目的端口}      │
│ 即: 四元组 (4-Tuple)                             │
│                                                  │
│ 场景: 手机用户浏览网页                           │
│                                                  │
│ WiFi 环境:                                       │
│ 连接 = {192.168.1.100:54321 → 142.250.80.46:443}│
│ ↑                                                │
│ [用户走出咖啡厅, WiFi断开, 切换到 4G]             │
│                                                  │
│ 4G 环境:                                        │
│ 新地址= {10.20.30.40:12345 → 142.250.80.46:443} │
│ ↑ 四元组变了!                                    │
│                                                  │
│ 结果: TCP 认为这是一个全新的连接!                 │
│       → 原连接失效!                               │
│       → 需要重新三次握手 + TLS握手!               │
│       → 页面加载中断, 用户感知到卡顿/白屏          │
└─────────────────────────────────────────────────┘

QUIC 的解决方案: Connection ID (CID)

┌─────────────────────────────────────────────────┐
│ QUIC 连接标识 = 64位随机 Connection ID          │
│ (与 IP/端口无关!)                                │
│                                                  │
│ 场景: 同样是手机切换网络                          │
│                                                  │
│ WiFi 环境:                                       │
│ CID = 0x8a3f9c2b1e4d7f00                       │
│ 地址 = 192.168.1.100:54321 → 142.250.80.46:443  │
│ ↑                                                │
│ [用户走出咖啡厅, WiFi断开, 切换到 4G]             │
│                                                  │
│ 4G 环境:                                        │
│ CID = 0x8a3f9c2b1e4d7f00  (没变!)               │
│ 地址 = 10.20.30.40:12345 → 142.250.80.46:443   │
│ ↑ IP端口变了, 但 CID 相同!                       │
│                                                  │
│ 结果: 服务端通过 CID 识别出是同一个连接!           │
│       → 连接保持不断! ✅                          │
│       → 页面继续加载, 用户无任何感知               │
│       → 正在下载的文件不会中断                     │
│       → 视频通话不会掉线                           │
└─────────────────────────────────────────────────┘

连接迁移的完整过程:

Client (手机)                        Server (CDN)
   |                                     |
   |--- 通过 WiFi 正常通信 ---           |
   |   CID=0xABC, Path #1              |
   |                                     |
   | [检测到 WiFi 信号变弱]              |
   | [切换到 4G 网络]                   |
   |                                     |
   |--- 使用 NEW_CONNECTION_ID + ------|
   |    Path Challenge (新路径探测) -->|
   |   (携带新的 CID, 通过 4G IP发出)    |
   |                                     |
   |<-- Path Response (确认新路径OK) ---|
   |   (证明: 你真的拥有这个新IP)        |
   |                                     |
   | [开始在新路径上发送数据]             |
   |--- STREAM data via 4G ----------->|
   |                                     |
   | [旧的 WiFi 路径超时后自动关闭]       |
   | (不需要 TIME_WAIT!)                |

为什么 Connection ID 能防劫持?

CID 是一个 64 位随机数,在握手中协商。即使攻击者知道了你的新 IP 地址,也无法伪造 QUIC 包,因为他不知道正确的 CID(而且大部分包内容还是加密的)。

协议阶段流转图

QUIC 连接生命周期:

  Client                                    Server
    |                                         |
    |  ══════════ 握手阶段 ══════════          |
    |                                         |
    |----- Initial (Client Hello) ---------->│
    |       Long Header                       │
    |       包含: TLS ClientHello             │
    |             QUIC 参数                    │
    |                                         |
    |<---- Initial (可能带 Retry) -----------│
    |       版本协商 / DoS 防护验证            │
    |                                         |
    |----- Initial (带 Retry Token) -------->│
    |                                         |
    |<---- Handshake (Server Hello+) --------│
    |       Long Header                       │
    |       包含: TLS ServerHello              │
    |             Certificate(s)               │
    |             Finished (加密握手完毕)       │
    |                                         |
    |----- Handshake (Client Finished) ------>│
    |       Long Header                       │
    |                                         |
    |  ═════ 1-RTT 密钥已建立, 可以加密通信 ═════  │
    |                                         |
    |<==== 1-RTT (Short Header) ==============│
    |     (可能是: 加密的响应数据 + Finished)    │
    |                                         |
    |  ═════════ 数据传输阶段 ═══════════       │
    |                                         |
    |===== Short Header STREAM 数据 ========>│
    |<==== Short Header STREAM 数据 =========│
    |     (多个 Stream 并行传输, 无 HOL阻塞)    │
    |                                         |
    |  ═════ 可选: 连接迁移 ═════              │
    |                                         |
    |     [网络环境变化: WiFi → 4G]           │
    |                                         |
    |===== NEW_CONNECTION_ID + Challenge ===>│  (新路径)
    |<==== Path Response ===================│
    |     (迁移完成, 新路径生效)               │
    |                                         |
    |  ═════════ 连接关闭 ═════════════        │
    |                                         |
    |----- CONNECTION_CLOSE (Short Header) ->│
    |       (包含错误码和原因短语)             │
    |                                         |
    |     [立即关闭, 无 TIME_WAIT!]           │
    |     (因为是 UDP, 不占用四元组)           │
    |                                         │

包类型速查:
┌────────────────────┬──────────┬──────────────────────────┐
│ 包类型             │ Header   │ 说明                     │
├────────────────────┼──────────┼──────────────────────────┤
│ Initial            │ Long     │ 握手启动, 必须回显Token   │
│ 0-RTT              │ Long     │ 握手前提前发的数据        │
│ Handshake          │ Long     │ 加密握手参数交换          │
│ Retry              │ Long     │ 服务端验证客户端身份      │
│ 1-RTT              │ Short    │ 握手完成后加密的数据      │
│ (其他 Short)       │ Short    │ 正常应用数据帧            │
│ CONNECTION_CLOSE   │ Short    │ 关闭连接                  │
└────────────────────┴──────────┴──────────────────────────┘

HTTP/3 与 QUIC 的关系

┌─────────────────────────────────────────────────────┐
│                  HTTP/3 (应用层协议)                  │
│  RFC 9110 (语义) + RFC 9114 (映射到QUIC)             │
│                                                     │
│  特性:                                              │
│  ├── 语义与 HTTP/2 兼容 (方法/状态码/头部字段)        │
│  ├── QPACK 头部压缩 (优化 HPACK, 解决队头阻塞)       │
│  └── Server Push 支持                                │
├─────────────────────────────────────────────────────┤
│                  QUIC (传输层协议)                    │
│  RFC 9000 (核心) / RFC 9001 (TLS) / RFC 9002 (恢复)  │
│                                                     │
│  特性:                                              │
│  ├── Stream 多路复用 (无队头阻塞)                     │
│  ├── TLS 1.3 内置加密 (端到端)                        │
│  ├── Connection ID 连接迁移                           │
│  ├── 0-RTT / 1-RTT 快速握手                          │
│  └── 可插拔拥塞控制 (BBR/Cubic/NewReno)              │
├─────────────────────────────────────────────────────┤
│                    UDP (底层传输)                      │
│  RFC 768                                             │
│                                                     │
│  仅负责将数据报尽力送达                               │
└─────────────────────────────────────────────────────┘

对应关系:
HTTP/1.1 → TCP
HTTP/2   → TCP + TLS
HTTP/3   → QUIC (内部自带 TLS 1.3, 底层用 UDP)

QPACK vs HPACK:

HTTP/2 用 HPACK 做头部压缩,但 HPACK 有一个问题,它依赖有序传输(in-order delivery),导致解压必须按序进行。这在 TCP 上没问题(反正也有 HOL),但在 QUIC 的多 Stream 模型下反而引入了新的阻塞点。

QPACK 解决了这个问题:允许各 Stream 独立解码自己的头部,彻底消除了压缩层面的队头阻塞。

用命令行测试 QUIC 连接

# ============================================
# 方法 1: 使用 curl 测试 QUIC/HTTP/3 连接
# ============================================

# curl 7.66+ 版本支持 --http3 选项
# 检查你的 curl 版本
curl --version | grep -i http3
# 如果没有输出, 说明当前版本不支持 HTTP/3

# 测试某个网站是否支持 QUIC/HTTP/3
curl --http3 -I --connect-timeout 5 https://www.google.com 2>&1 | head -10

# 成功支持 QUIC 时, 输出示例:
# HTTP/3 200
# content-type: text/html; charset=UTF-8
# alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
#                                           ^^^^^^^^^^^^
#                              这个 alt-svc 头声明了 QUIC 支持!

# 不支持 QUIC 时会自动降级到 HTTP/1.1 或 HTTP/2:
curl --http3 -I --connect-timeout 5 https://www.example.com 2>&1 | head -5
# 可能输出: HTTP/2 400 (或自动降级警告)

# ============================================
# 方法 2: 使用 dig 查看 DNS HTTPS/SVC 记录
# ============================================

# HTTPS 记录 (RFC 9460) 告诉客户端目标站点支持的协议和参数
dig HTTPS www.google.com +short

# 输出示例:
# 1 . "h3-29=:443"; alpn=h3
# 解释: 支持 QUIC (h3-29), 端口 443, ALPN 协商名 h3

# SVCB 记录 (更详细的版本):
dig SVCB _https.www.google.com +short

# 输出示例:
# 1 . "h3-29=:443"; alpn=h3; ipv4hint=142.250.x.y

# ============================================
# 方法 3: 使用 openssl s_client 测试 TLS 1.3 握手
# ============================================

# QUIC 使用 TLS 1.3, 可以测试目标是否支持 TLS 1.3
openssl s_client -connect www.google.com:443 -tls1_3 </dev/null 2>/dev/null | grep "Protocol"

# 输出: Protocol  : TLSv1.3
# 如果显示 TLSv1.2 或更低, 说明该服务器的 QUIC 配置可能有特殊处理

# ============================================
# 方法 4: 查看系统 QUIC 相关的网络配置
# ============================================

# Linux: 查看 UDP 缓冲区 (QUIC 基于 UDP, 受此限制)
sysctl net.core.rmem_max
sysctl net.core.wmem_max
# 建议: 高性能 QUIC 服务器应调大到 25MB+
sudo sysctl -w net.core.rmem_max=26214400
sudo sysctl -w net.core.wmem_max=26214400

# macOS: 查看相同参数
sysctl net.inet.udp.recvspace
sysctl net.udp.maxdgram

全面对比:谁才是你的最佳选择?

KCP vs TCP 详细对比(12 维度)

#对比维度KCPTCP优胜者
1连接建立无需握手,直接发送三次握手(至少 1 RTT)KCP
2首包延迟接近 0 ms(无握手)至少 1.5 RTT (30100ms)KCP
3平均传输延迟低(快速重传,<30ms恢复)较高(等 RTO 或 3 个 dupACK,200ms+)KCP
4最大传输延迟远低于 TCP(约 TCP 的 1/10)可能很高(超时指数退避可达数秒)KCP
5吞吐量可更高(关闭拥塞控制时全速发送)受限于内核拥塞控制算法看场景
6丢包恢复速度快(主动快速重传,不等超时)慢(RTO 通常 200ms+)KCP
7公平性(拥塞控制)可完全关闭(可能抢占带宽)强制执行(保证网络公平性)TCP
8安全性原生无加密,需上层叠加 AES/salsa20 等可选 TLS(成熟生态,一键启用)TCP(更便捷)
9队头阻塞(HOL)单流模型,存在 HOL 阻塞单字节流,存在 HOL 阻塞平手
10连接迁移不支持(绑定四元组)不支持(绑定四元组)平手
11部署兼容性需集成 KCP SDK(C/C++/Go/Java 等多语言绑定)OS 原生支持(所有操作系统内置)TCP
12内核实现用户空间库实现(应用层可控)内核协议栈(系统级优化,零拷贝等)TCP
13生态成熟度 / 标准化中高(国内广泛使用),但 无 RFC极高(40 年积累,RFC 793 系列)TCP

KCP vs TCP 结论:

选择 KCP 当你需要:
✅ 极致的低延迟 (游戏操作、音视频通话、实时交易)
✅ 自定义拥塞控制策略 (或完全关闭)
✅ 在弱网环境下更快的丢包恢复
✅ 应用层对传输过程的完整控制

继续使用 TCP 当你需要:
✅ 通用可靠传输 (文件下载、API 调用、数据库同步)
✅ 开箱即用的安全性 (TLS 一行配置)
✅ 全平台原生支持 (无需集成额外 SDK)
✅ 网络公平性 (不做"破坏者")
✅ 40 年沉淀的稳定性与生态

QUIC vs TCP (+ TLS) 详细对比(12 维度)

#对比维度QUICTCP + TLS优胜者
1握手 RTT1 RTT(首次)/ 0 RTT(复用)2.53.5 RTT(TCP + TLS 握手)QUIC
2首字节时间 (TTFB)~1 RTT / ≈0(客户端发送)34 RTTQUIC
3加密方式TLS 1.3 内置(协议层面强制)TLS 外挂于应用层(可选)QUIC
4安全模型端到端加密(含大部分头部字段)仅加密载荷(TCP 头部明文)QUIC
5多路复用Stream 多路复用,各 Stream 独立交付单字节流(应用层自行分帧如 HTTP/2)QUIC
6队头阻塞 无 HOL(各 Stream 独立 ACK)有 HOL(一个丢包阻塞全部)QUIC
7连接迁移 支持(Connection ID 标识)不支持(四元组标识)QUIC
8拥塞控制可插拔(BBR/Cubic/NewReno,用户态配置)内核固定(需升级内核才能换算法)QUIC
9丢包恢复机制自定义 ACK/SACK/FEC(类似 TCP 但可优化)成熟(SACK/RACK/TLP,数十年打磨)平手(各有优势)
10安全性便利性默认开启,无法关闭(开发者无需操心)需要显式配置 TLS(容易遗漏)QUIC
11部署兼容性需要 OS / 浏览器 / 库支持(老旧设备不支持)所有联网设备原生支持TCP
12连接结束开销立即关闭(无 TIME_WAIT)TIME_WAIT(等待 2MSL)QUIC
13标准化程度高(RFC 9000/9001/9002,HTTP/3 标准)极高(RFC 793 系列,互联网基石)平手(都是标准)

QUIC vs TCP 结论:

选择 QUIC (HTTP/3) 当你需要:
✅ 最快的 Web 体验 (1RTT/0RTT 握手)
✅ 移动端无缝体验 (WiFi ↔ 4G 切换不掉线)
✅ 默认的安全 (不用操心 TLS 配置)
✅ 解决 HTTP/2 的队头阻塞问题
✅ 灵活的拥塞控制策略

继续使用 TCP(+TLS) 当你需要:
✅ 兼容所有设备 (包括 IoT 设备、旧版系统)
✅ 企业防火墙友好 (部分网络会阻断 UDP)
✅ 调试工具链成熟 (tcpdump/Wireshark 完美支持)
✅ 运维经验丰富 (40 年积累的排查体系)

综合适用场景矩阵

              安全要求
          低 ◄────────────────────► 高

   ┌───────────┬───────────┬───────────┬───────────┐
 大 │           │           │           │           │
 吞 │   UDT     │   KCP     │  KCP+AES  │   QUIC    │
 量 │  (大数据)  │  (低延迟)  │(延迟+安全)│ (Web标准)  │
 ↓  │           │           │           │           │
   ├────────────┼───────────┼───────────┼───────────┤
 中 │           │  enet     │   KCP     │   QUIC    │
 等 │   TCP/RUDP │ (游戏轻量) │(音视频/   │ (HTTPS/   │
 待 │ (通用/教学)│ RakNet    │ 游戏/VPN) │  API)     │
 安 │           │ (Unity)   │           │           │
 全 │           │           │           │           │
 ↑  └───────────┴───────────┴───────────┴───────────┘
   低 ◄────────────────────────────► 高
               延迟要求

各方案一句话速查:

┌──────────┬──────────────────────────────────────────────────────────┐
│   TCP    │ 通用可靠传输的默认选择 — 文件下载/API/数据库/邮件         │
│   KCP    │ 延迟敏感型应用的首选 — 音视频/实时游戏/VPN加速器        │
│   QUIC   │ Web 时代的未来标准 — HTTPS/HTTP3/WebSocket 替代方案      │
│   UDT    │ 大规模数据传输 — 科学计算/地理信息/卫星数据/备份同步     │
│   enet   │ 轻量级游戏网络层 — MOBA/FPS/独立游戏项目                 │
│ RakNet   │ Unity/虚幻引擎游戏的完整网络解决方案                     │
│   RUDP   │ 学习可靠传输原理的教学示例 — ARQ/GN/SW 协议入门        │
└──────────┴──────────────────────────────────────────────────────────┘

总结

今天我们系统梳理了从 UDP 协议本身 到 基于 UDP 的可靠传输方案 的完整知识体系:

UDP 协议本身:

  • 仅 8 字节固定头部(源端口/目的端口/长度/校验和)
  • 五大特性:无连接、不可靠、低开销、支持广播多播、面向报文
  • 天然适用于 DNS/DHCP/NTP/VoIP/游戏等场景
  • 但不可靠性限制了它的适用范围,催生了各种”可靠化”方案

六大可靠传输方案全景:

方案一句话定位最佳场景安全性
RUDPARQ 教学级实现学习原理无
UDT大数据高速传输海量数据跨网传输可选
KCP低延迟可靠传输音视频/游戏/VPN需自建
QUIC下一代 Web 传输标准HTTPS/Web API内置 TLS
enet游戏轻量网络库MOBA/FPS可选 AES
RakNet游戏完整网络框架Unity/UE 项目插件

KCP 重点回顾:

  • 设计哲学:以延迟换可靠性(vs TCP 以带宽换可靠性)
  • 核心优势:ARQ 快速重传(不等超时)、nodelay 模式(可配 4 参数)、可关闭拥塞控制
  • 性能提升:平均延迟降低约 35%,最大延迟降低约 90%
  • 必须注意:KCP 原生不包含任何加密机制,生产环境务必在 KCP 之上叠加 AES/salsa20/ChaCha20 等加密层
  • KCP 没有 RFC 规范,源自 skywind3000 的开源 GitHub 项目

QUIC 重点回顾:

  • 发展历程:Google gQUIC (2013) → IETF 标准化 → RFC 9000 系列 (2021) → HTTP/3 (2022)
  • 核心优势:1-RTT 握手(首次)/ 0-RTT 恢复(复用)
  • TLS 1.3 内置强制加密(端到端,含头部字段)
  • Stream 多路复用彻底消除队头阻塞
  • Connection ID 支持连接迁移(WiFi ↔ 4G 无缝切换)
  • 已成为 HTTP/3 (RFC 9114) 的底层传输协议,全球 30%+ Web 流量已采用

与 TCP 的关系,不会取代,而是互补:

场景推荐选择理由
通用 Web 服务TCP + TLS 或 QUIC/HTTP-3新浏览器优先 QUIC,旧设备回退 TCP
实时音视频/在线游戏KCP + 自建加密延迟敏感是第一优先级
文件下载 / 大数据TCP 或 UTD吞吐量优先,长肥管道选 UDT
VPN / 网络加速器KCP + 加密抗丢包能力 + 可关拥控 = 弱网利器
物联网 / 嵌入式TCP / CoAP over UDP资源受限,TCP 更省心
移动端 App(频繁切网)QUIC连接迁移是杀手特性

最终建议:如果你在做一个新项目,先问自己三个问题:

  1. 延迟有多重要? → 很重要 → 考虑 KCP 或 QUIC
  2. 安全有多重要? → 很重要 → QUIC(自带 TLS)或 KCP+自建加密
  3. 需要兼容什么环境? → 只需现代浏览器 → QUIC;需全平台 → TCP;需自控客户端 → KCP

没有银弹。理解每个方案的 trade-off,才能做出最适合自己的技术决策。


参考资源

UDP 相关 RFC

  • RFC 768 — User Datagram Protocol(UDP 协议标准定义,1980)
  • RFC 1122 — Requirements for Internet Hosts(UDP 校验和细节说明)

QUIC 相关 RFC(IETF 国际标准)

  • RFC 9000 — QUIC: A UDP-Based Multiplexed and Secure Transport(QUIC 核心协议规范)
  • RFC 9001 — Using TLS to Authenticate QUIC(QUIC 与 TLS 1.3 的整合规范)
  • RFC 9002 — QUIC Loss Detection and Congestion Control(QUIC 丢包检测与拥塞控制)
  • RFC 9114 — HTTP/3(基于 QUIC 的 HTTP 语义映射)

TCP 相关 RFC(对比参考)

  • RFC 793 — Transmission Control Protocol(TCP 协议标准)
  • RFC 5681 — TCP Congestion Control(拥塞控制:慢启动/拥塞避免/快重传/快恢复)
  • RFC 6298 — Computing TCP’s Retransmission Timer(RTO 计算规范)
  • RFC 896 / RFC 1122 — Congestion Control in IP/TCP Interconnections(Nagle 算法 / TCP_NODELAY)
  • RFC 1323 — TCP Extensions for High Performance(窗口缩放 / 时间戳 / PAWS)
  • RFC 2018 — TCP Selective Acknowledgment Options(SACK 选择性确认)
  • RFC 6928 — Increasing TCP’s Initial Window(初始拥塞窗口优化)

KCP 参考资料( 非官方 RFC 规范)

其他参考资料

延伸阅读推荐 O3