Appearance
TCP、UDP 与 QUIC
TCP 与 UDP 的区别
| 维度 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接,通信前建立状态 | 无连接,直接发送数据报 |
| 可靠性 | 序号、确认、重传、校验保证可靠、有序字节流 | 尽力而为;不保证送达、顺序、去重或重传 |
| 边界 | 面向字节流,应用需要自行定义消息边界 | 保留数据报边界 |
| 控制 | 具备流量控制和拥塞控制 | 协议本身没有;应用可自行实现 |
| 首部 | 至少 20 字节,信息更丰富 | 固定 8 字节 |
| 场景 | Web、数据库、文件传输、远程登录 | DNS、实时音视频、游戏、QUIC |
UDP 的“快”来自协议开销和等待更少,不代表它没有拥塞问题或总是更低延迟。实时系统常在应用层接受部分丢包,或按业务需要添加可靠机制。
TCP 如何保证可靠性
- 序列号与累计确认(ACK):接收端确认“下一个期望的字节序号”,发送端据此判断已确认数据。
- 校验和:发现报文段在传输中损坏时丢弃,依赖重传恢复。
- 超时重传(RTO):未在估计超时时间内收到确认时重传,并基于 RTT 动态估计 RTO。
- 快速重传:收到多个重复 ACK(经典阈值为 3 个)通常推断有报文丢失,无需等待 RTO 即重传。
- 滑动窗口:允许连续发送多个未确认字节;乱序段可缓存、重复段可去重,从而实现有序交付。
- 流量控制与拥塞控制:分别保护接收方和网络。
流量控制:接收窗口
接收方在 TCP 首部的窗口字段通告可接收空间 rwnd。发送方的在途未确认数据不能超过接收窗口,避免接收缓冲区被压垮。窗口为 0 时发送方暂停普通发送,并通过窗口探测避免“窗口更新丢失”而永久等待。
拥塞控制:拥塞窗口
拥塞控制的目标是保护网络,与接收方处理能力无关。有效发送窗口约为:
text
min(接收窗口 rwnd, 拥塞窗口 cwnd)经典 Reno 的面试表述:
- 慢启动:
cwnd从较小值开始,收到 ACK 后快速增长,近似每 RTT 翻倍; - 拥塞避免:达到
ssthresh后线性增长,近似每 RTT 增加一个 MSS; - 快速重传:多个重复 ACK 后立即重传疑似丢失报文;
- 快速恢复:认为仍有部分数据在网络中,不完全退回初始慢启动;阈值通常按丢失前窗口的一部分设置。
实际操作系统可能使用 CUBIC、BBR 等算法,具体行为不完全等同于 Reno。
TCP 三次握手
三次握手使双方都确认初始序号,并建立双向收发能力:
- 客户端 SYN 表明客户端希望建立连接且其发送路径能到达服务器;
- 服务端 SYN+ACK 确认收到客户端 SYN,同时发出自己的 SYN;
- 客户端 ACK 确认收到了服务端 SYN,服务端收到该 ACK 后也确认客户端能收到自己的报文。
为什么不是两次? 服务端仅回复 SYN+ACK 后,无法确认客户端是否收到自己的 SYN,也更难处理网络中延迟、重复的旧连接请求;第三次 ACK 让服务端再进入已建立状态。
TCP 四次挥手与 TIME_WAIT
TCP 是全双工字节流,一方发送 FIN 仅表示“我不再发送”,但仍可接收数据:
ACK 与对端 FIN 有时可合并为三段,但典型情况下服务端收到 FIN 后可能仍有数据要发送,因此 ACK 和自己的 FIN 分开发出,表现为四次。
主动关闭方进入 TIME_WAIT 的目的:
- 若最终 ACK 丢失,可重新确认对端重传的 FIN;
- 等待旧报文在网络中自然消失,避免与后续使用相同四元组的新连接混淆。
HTTP Keep-Alive 与 TCP Keepalive
两者不是一回事:
- HTTP Keep-Alive / 持久连接:应用层概念,在一个 TCP 连接(或 HTTP/2/3 连接)上连续承载多个 HTTP 请求/响应,减少建连成本。HTTP/1.1 默认持久连接,除非声明
Connection: close。 - TCP Keepalive:操作系统 TCP 栈的保活探测。空闲很久后发送探测包,检测对端是否失联;时间参数常为系统配置,默认往往很长。它不是 HTTP 请求复用机制。
UDP 如何实现可靠传输
在应用层按业务选择性补齐 TCP 的能力:给消息编号、校验和分片,接收方 ACK/NACK、乱序重排和去重,发送方基于 RTT 超时重传和退避,还应实现流量/拥塞控制。简单的停等协议易理解但吞吐量低;实际协议通常采用滑动窗口和选择确认。QUIC 就是在 UDP 上实现可靠流、多路复用、加密和拥塞控制的代表。
NAT 穿透:STUN、TURN 与 ICE
NAT 会把内网地址和端口映射成公网地址和端口。端点只知道自己的 192.168.x.x 地址时,公网对端无法直接向它发起连接;而且不同 NAT 类型对入站映射的保持方式不同,不能假设“知道公网 IP 就一定能直连”。
| 组件 | 解决的问题 | 不负责什么 |
|---|---|---|
| STUN | 让端点从服务器视角发现自己的公网映射(server-reflexive candidate) | 不保证双方一定能建立直连,也不转发媒体 |
| TURN | 直连失败时提供公网中继地址,由服务器转发数据 | 增加带宽、延迟和服务器成本 |
| ICE | 收集主机、STUN、TURN 候选并按连通性检查择优 | 不替代业务信令,候选信息仍需由信令通道交换 |
| SIP / WebSocket 等信令 | 交换会话描述、候选地址和鉴权信息 | 本身不是 NAT 穿透或媒体转发机制 |
典型流程是:信令交换候选 → ICE connectivity check(通常使用 STUN Binding 请求)→ 优先选择直连 → 失败后回退 TURN。对称 NAT、严格防火墙或 UDP 被屏蔽时,打洞可能失败;生产系统应准备 TURN 和 TCP/TLS 中继或其他降级路径。STUN/TURN/ICE 的协议定义分别见 RFC 8489、RFC 8656 和 RFC 8445。
面试判断可以归纳为:SIP 负责“谈成什么会话”,STUN 负责“发现映射”,TURN 负责“直连失败时中继”,ICE 负责“候选收集与择优”。
QUIC 与 HTTP/3
QUIC 是 IETF 定义的、运行在 UDP 之上的可靠、多路复用且默认加密的传输协议。核心规范包括:
- RFC 9000:连接、数据包、流、流量控制和迁移;
- RFC 9001:QUIC 与 TLS 1.3 的集成;
- RFC 9002:丢失检测和拥塞控制;
- HTTP/3 在 QUIC 之上承载 HTTP 语义,QUIC 本身并不等于 HTTP/3。
为什么基于 UDP,而不是直接修改 TCP
并不是因为“UDP 天然更快”,而是因为 UDP 只提供最小的数据报能力,便于 QUIC 在用户态重新实现传输语义:
- 可部署性:TCP 的连接状态、重传和拥塞控制主要在内核,升级往往依赖 OS 发布;QUIC 用户态库可随应用快速迭代;
- 避免协议僵化:大量中间设备会按固定 TCP 行为检查或改写报文,直接扩展 TCP 容易被丢弃;UDP 载荷加密后,中间设备更难依赖内部字段形成错误假设;
- 统一握手:把传输参数协商与 TLS 1.3 密钥协商合并,减少建立安全连接的往返;
- 实现流级多路复用:不受 TCP“整个连接只有一个有序字节流”的限制。
代价是可靠性、丢失恢复、拥塞控制、路径 MTU、连接管理和安全防护都要由 QUIC 实现;UDP 也可能被企业网络、防火墙或运营商限速、丢弃,因此生产系统通常保留 TCP/HTTP/2 降级路径。
建连与密钥阶段
首次连接时,客户端的 QUIC Initial 包携带 TLS ClientHello;服务端回复 Initial/Handshake 数据,双方协商传输参数并派生密钥。完成一次网络往返后,客户端通常即可发送受 1-RTT 密钥保护的应用数据:
与传统 TCP + TLS 1.3 相比:
text
TCP + TLS:先 TCP 三次握手(1 RTT),再 TLS 握手(通常再 1 RTT)
QUIC:传输握手与 TLS 1.3 握手合并,首次连接通常 1 RTT0-RTT:更低延迟,但可能重放
客户端持有先前连接的会话票据和服务器参数时,可以在首个发送批次中同时发送 0-RTT 包和应用数据,不等待服务器响应。但 0-RTT 不提供防重放保证:攻击者可能复制并重新发送早期数据。
因此只应承载可安全重放或具有业务幂等保护的操作,例如查询、幂等配置拉取;登录扣款、创建订单、发放奖励等有副作用请求必须禁用 0-RTT,或使用幂等键、nonce、时间窗口和服务端去重。
包、帧与流
QUIC 将传输语义分为三层:
text
UDP Datagram
└── QUIC Packet(包号、密钥阶段)
└── Frame(ACK、STREAM、CRYPTO、MAX_DATA、PING、PATH_CHALLENGE...)
└── Stream Data(应用有序字节流的一部分)- QUIC 包使用包号空间跟踪发送与确认;包号不会像 TCP 32 位序列号那样在连接内回绕复用;
- ACK 可以携带多个确认范围,适合表达乱序与间断接收;
- 丢失后通常是把未确认的 frame 内容装入带新包号的数据包,而不是原样重传旧包;
- stream 分为单向和双向;每条 stream 内保证有序交付,不同 stream 之间没有传输层的顺序依赖。
为什么能减少队头阻塞
HTTP/2 虽能在一个 TCP 连接中复用多个请求,但 TCP 必须先补齐字节流中的丢失段,后续所有 stream 的数据都无法交付,形成连接级队头阻塞。
QUIC 在 stream 级别维护偏移和重组:某条 stream 丢失数据时,只阻塞该 stream 的有序交付,其他 stream 已完整到达的数据仍可交付应用。
需要注意:
- 同一 stream 内仍然有队头阻塞;
- 所有 stream 通常共享连接级拥塞窗口,丢包导致拥塞窗口缩小时整体发送能力都会下降;
- HTTP/3 的 QPACK 动态表依赖也可能让特定请求头暂时等待,但不再是 TCP 丢一个包就阻塞整个连接的模式。
可靠性与丢失检测
QUIC 自己实现可靠传输:
- ACK range 确认收到的包号区间;
- 基于 RTT、包号间隔和 PTO(Probe Timeout)判断丢失并探测路径;
- STREAM/CRYPTO 等需要可靠送达的 frame 在丢失后重新发送;
- 重复 frame 可通过 stream offset、包号等去重;
- 标准 IETF QUIC 不把早期 gQUIC 的 FEC 作为基础必备能力。
QUIC 的 ACK 与 TCP ACK 都用于可靠性反馈,但不能直接把 TCP 的“累计确认字节序号、3 个重复 ACK”规则机械套到 QUIC。
两级流量控制与拥塞控制
QUIC 有两级接收方流量控制:
- 连接级:
MAX_DATA限制全部 stream 合计可发送的数据; - stream 级:
MAX_STREAM_DATA防止单条 stream 占满全部接收缓冲; MAX_STREAMS还限制对端可创建的单向/双向 stream 数量。
发送方达到额度后会进入 DATA_BLOCKED 或 STREAM_DATA_BLOCKED 状态,等待接收端提高上限。流量控制保护接收端内存,不能替代拥塞控制。
拥塞控制保护网络,QUIC 为实现提供 ACK、RTT 和丢失反馈。RFC 9002 给出 NewReno 风格的示例算法,实际实现也可采用 CUBIC、BBR 等算法;同一连接的多条 stream 一般共享拥塞控制状态。
TLS 1.3 是 QUIC 的组成部分
QUIC 不是“先建立 QUIC,再在上面跑普通 TLS record”:
- TLS 1.3 握手消息通过 QUIC CRYPTO frame 传递;
- TLS 派生的密钥直接用于保护 QUIC 包;
- QUIC 同时协商最大数据量、最大 stream 数、空闲超时等传输参数;
- 服务器必须认证,客户端认证可选;
- Initial 包的保护不能替代服务器身份认证,它主要避免明文中间设备干预并统一握手流程。
加密更多传输元数据提高了安全和协议可演进性,但也降低传统抓包工具的可观测性。工程上常结合 qlog、连接指标、密钥日志和应用层 trace 排查。
连接 ID 与连接迁移
TCP 连接通常由四元组(源/目的 IP 和端口)标识,移动网络从 Wi-Fi 切到蜂窝后,IP 变化会使连接失效。QUIC 使用 Connection ID 标识连接状态,使客户端地址变化或 NAT 重新绑定后仍可继续连接。
迁移并非直接信任新地址:端点通过 PATH_CHALLENGE / PATH_RESPONSE 验证新路径,并为新路径重新评估 RTT、拥塞窗口和 MTU。Connection ID 还可帮助负载均衡器把数据包路由到持有连接状态的后端,但编码路由信息时要考虑隐私、密钥轮换和后端扩缩容。
地址验证与抗放大攻击
UDP 无连接,攻击者可伪造源地址诱导服务器向受害者发送大量数据。QUIC 在地址验证前限制服务器发送量:服务器通常最多发送收到字节数的 3 倍;需要时可发送 Retry,让客户端携带 token 再次发起 Initial,证明它能接收该地址上的响应。
服务端还要限制:半开连接数、每 IP/网段速率、token 有效期、握手 CPU、未验证地址的缓冲区,以及 Initial 包解析错误的日志放大。
QUIC DATAGRAM 与游戏实时消息
QUIC stream 是可靠、有序字节流,不适合所有实时消息。RFC 9221 的 QUIC DATAGRAM 扩展允许在同一安全连接中发送不可靠、不重传的数据报:
| 数据类型 | 推荐承载方式 |
|---|---|
| 登录、背包变更、聊天、可靠指令 | 可靠 QUIC stream |
| 位置快照、瞄准方向、可被新状态覆盖的帧 | QUIC DATAGRAM |
| 大文件/资源下载 | 独立可靠 stream,避免和控制流互相阻塞 |
DATAGRAM 不重传,但仍受连接的拥塞控制和最大数据报大小约束。它不会自动解决状态同步、乱序、去重和快照基线问题,业务仍需消息序号和时效判断。
HTTP/3 如何使用 QUIC
HTTP/3 把每个请求/响应映射到 QUIC stream,并使用独立的控制 stream 以及 QPACK 编码/解码 stream:
- 多请求不再共享一个 TCP 有序字节流;
- QPACK 取代 HTTP/2 的 HPACK,减少动态头表造成的连接级阻塞;
- HTTP 语义、状态码和缓存规则没有因为传输层变化而消失;
- WebTransport 可在 HTTP/3/QUIC 上同时提供 stream 和 datagram,适合浏览器实时应用。
| 维度 | HTTP/2 + TCP + TLS | HTTP/3 + QUIC |
|---|---|---|
| 安全建连 | TCP 与 TLS 分阶段 | QUIC 与 TLS 1.3 合并 |
| 多路复用 | HTTP stream 共享 TCP 字节流 | QUIC 原生独立 stream |
| 丢包影响 | TCP 连接级队头阻塞 | 通常只阻塞相关 stream,但共享拥塞窗口 |
| 网络切换 | 四元组变化通常重连 | Connection ID + 路径验证可迁移 |
| 协议升级 | 常依赖内核 TCP 栈 | 用户态库可快速升级 |
服务器开发与性能边界
QUIC 在用户态处理 UDP、加密、重传和拥塞控制,服务器通常需要关注:
- UDP socket 的收发缓冲、
recvmmsg/sendmmsg或 io_uring 批处理; - UDP GSO/GRO、ECN、路径 MTU 探测与避免 IP 分片;客户端 Initial UDP 数据报至少为 1200 字节;
- 每个连接的 packet number、ACK range、stream、重传队列和定时器状态;
- TLS/AEAD 的 CPU 成本,使用硬件加速和批量加解密;
- Connection ID 路由、无状态重置(Stateless Reset)、版本协商和灰度升级;
- UDP 被阻断时的 TCP/HTTP/2 fallback;
- qlog、握手失败率、0-RTT 接受率、丢包率、PTO、迁移次数、流控阻塞和 P95/P99。
QUIC 不保证在所有网络上都比 TCP 更快:连接少且长生命周期、网络几乎不丢包、UDP 被限速,或用户态加密/协议实现尚未优化时,收益可能不明显甚至更差。
Linux 下的 select/poll/epoll/io_uring 事件循环、收发批处理与背压设计,见 网络服务器 I/O。
面试速答
QUIC 基于 UDP 不是因为 UDP 自带可靠性,而是为了绕开内核 TCP 和中间设备僵化,在用户态实现可靠传输、拥塞控制、TLS 1.3 和多 stream。首次安全建连通常 1-RTT,恢复连接可 0-RTT,但 0-RTT 有重放风险。QUIC 的每条 stream 内有序、不同 stream 独立重组,因此减少 TCP 的连接级队头阻塞;但共享拥塞窗口,同一 stream 内仍会阻塞。Connection ID 支持 NAT 重绑定和网络迁移,新路径需要 PATH_CHALLENGE 验证。HTTP/3 是运行在 QUIC 上的 HTTP;实时游戏还可用 QUIC DATAGRAM 承载无需重传的状态快照。