Files
cs-note/hhs/NETWORK/04-传输层/06-UDP协议与SCTP.md
T
2026-05-24 11:42:38 +08:00

233 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [计算机网络, UDP, SCTP]
create time: 2026-05-18 02:20
---
# UDP 协议与 SCTP
## 概述
UDP 是 TCP 的对立面:没有连接、不可靠、无拥塞控制——但正因如此,它在延迟敏感场景和无差错检测场景中不可替代。SCTP 则是被低估的"第三条路",结合了 TCP 和 UDP 的优势。
## UDP 首部格式
```
┌─────────────┬─────────────┐
│ Source Port │ Destination Port │ 各 2 bytes
├─────────────┼─────────────┤
│ Length │ Checksum │ 各 2 bytes
└─────────────┴─────────────┘
总长: 8 bytes(固定!)
```
| 字段 | 说明 |
|------|------|
| Source/Dest Port | 同 TCP |
| **Length** | UDP 包总长(含首部),最小 8 字节 |
| **Checksum** | 可选!(IPv4 中如果为 0 表示无校验;IPv6 中强制启用) |
### 极简首部的代价
```
TCP 20B 首部 vs UDP 8B 首部
一个 IP 包 = IP(20) + TCP(20) + Data = 40B overhead + N bytes payload
IP(20) + UDP(8) + Data = 28B overhead + N bytes payload
UDP 节省: 12 bytes / packet × 每秒数据包数 = 显著的带宽节省
```
## UDP 核心特性
| 特性 | UDP | TCP |
|------|-----|-----|
| 连接 | ❌ 无连接 | ✅ 三次握手 |
| 可靠性 | ❌ 不保证送达 | ✅ ACK + 重传 |
| 顺序保证 | ❌ 可能乱序到达 | ✅ 有序 |
| 流量控制 | ❌ | ✅ 滑动窗口 |
| 拥塞控制 | ❌ | ✅ 慢启动/快恢复等 |
| 头部开销 | 8 bytes | 20-60 bytes |
| 半双工/全双工 | N/A(无方向概念) | 全双工 |
| 流模式/报文模式 | **数据报模式**(保留边界) | 字节流模式 |
### 为什么 UDP 保留消息边界?
```go
// UDP: send("HELLO") → recv() = "HELLO"
// 一条 UDP datagram 就是一个完整消息
// TCP: write("H"); write("E"); write("L"); write("L"); write("O")
// read() 可能是 "HE", "LLO", "HELL", "LLO"...
```
## UDP 适用场景
### 典型应用
| 应用 | 为什么用 UDP? |
|------|---------------|
| **DNS** | 查询很短(通常 < 512 bytes),等待重传得不偿失 |
| **NTP** | 最新时间最重要,旧值无意义 |
| **VoIP / WebRTC** | 丢一帧比延迟卡顿更可接受 |
| **在线游戏** | 每秒几十次状态同步,延迟比可靠更重要 |
| **DHCP** | 客户端无 IP,无法使用 TCP(需要三次握手)|
| **SNMP** | 监控轮询,丢一次下轮重来即可 |
| **Redis** | 多数操作幂等或客户端自行重试 |
### DNS 用 UDP 还是 TCP?
| 情况 | 协议 | 原因 |
|------|------|------|
| 标准查询 (< 512B) | UDP port 53 | 快,RTT + 少量开销 |
| Zone Transfer (AXFR) | TCP port 53 | 传输大量记录,需可靠 |
| Response > 512B (RFC 5102) | EDNS0 + UDP | 扩展至 4096B |
| EDNS0 不支持,response 大 | TCP fallback | 设置 TC (Truncation) 位,客户端自动切 TCP |
```bash
$ dig example.com +trace # 默认走 UDP
$ dig example.com +tcp # 强制 TCP
$ host -t A example.com # 系统级 dig
```
## UDP 的可靠性增强
虽然 UDP 本身不提供可靠性,但许多基于 UDP 的应用自行实现了轻量级可靠传输:
```mermaid
flowchart LR
App["应用层"] --> U["UDP Datagram"]
subgraph "用户态实现(自加可靠性)"
U --> QUIC["QUIC<br/>内置 ACK+重传+流控"]
U --> DTLS["DTLS<br/>TLS over UDP"]
U --> RTP["RTP/RTCP<br/>媒体流+反馈"]
U --> Custom["自定义可靠协议<br/>如 uTP, KCP, Nakadi"]
end
QUIC -.->|"最终都到"| IPv4["IP"]
DTLS -.-> IPv4
RTP -.-> IPv4
Custom -.-> IPv4
```
### KCP 协议(国内广泛使用)
由俄罗斯开发者 Stan 设计,专为网游低延迟优化:
```
KCP vs TCP in game scenario:
──────────────────────────
TCP: 丢包 → RTO ≥ 1s → 卡顿
KCP: 丢包 → ARQ 立即重传 → < 100ms 恢复
FEC(前向纠错) 补包 → 无需等待确认
```
```go
import "github.com/xtaci/kcp-go"
// Go 中使用 KCP
sess, _ := kcp.Listen(":8080")
conn, _ := sess.AcceptKCP()
conn.SetNoDelay(1, 10, 2, 1) // 无Nagle, 10ms interval, 2 rounds, 1 SSThresh
conn.SetWindowSize(1024, 1024)
conn.SetMtu(1200)
```
## SCTP(Stream Control Transmission Protocol)
### 为何存在?
SCTP (RFC 4960) 试图结合 TCP 和 UDP 的优点:
| 特性 | TCP | UDP | **SCTP** |
|------|-----|-----|---------|
| 可靠性 | ✅ | ❌ | ✅ (可选) |
| 顺序保证 | ✅ 全局有序 | ❌ | ✅ **多流独立有序** |
| 多宿支持 | ❌ 单 IP | ❌ | ✅ 多 IP 冗余 |
| 消息边界 | ❌ 字节流 | ✅ 报文 | ✅ 报文 |
| 拥塞控制 | ✅ | ❌ | ✅ |
| 适用场景 | HTTP/Web | DNS/VoIP | **信令(Diameter/SIP)** |
### Multi-streaming(多流)— SCTP 的王牌特性
传统 TCP 的一个问题:**head-of-line blocking**。如果一个包丢了,所有后续消息都要等它重传完。SCTP 通过多流解决这个问题:
```
SCTP 连接拥有 3 个独立 stream:
Stream 0: [Data_0_A][Data_0_B][Data_0_C] ← 信令通道 (有序)
Stream 1: [Data_1_A] [Data_1_C] ← 聊天消息 (无序 OK)
Stream 2: [Data_2_A][Data_2_B] ← 文件传输
即使 Stream 0 的 B 丢失了 → Stream 1 和 2 继续前进!
```
```
四元组 vs 六元组:
TCP: src_ip, src_port, dst_ip, dst_port → 一个连接
SCTP: src_ip, src_port, dst_ip, dst_port → association (关联)
+ stream_id → stream within association
```
### SCTP 的使用场景
| 场景 | 说明 |
|------|------|
| **Diameter / SIP 信令** | 3GPP/ITU-T 标准强制使用 SCTP over IP |
| **SS7 over IP (SIGTRAN)** | 电信信令迁移到 IP 网络 |
| **数据库复制** | Oracle Data Guard 可配置 SCTP 连接 |
| **游戏服务器** | 多玩家流独立,互不影响 |
### Go 中的 SCTP 支持
```go
import "golang.org/x/net/sctp"
// 创建 SCTP 关联
conn, _ := sctp.DialSCTP("ip4:sctp", "&lt;local&gt;:3868", "&lt;peer&gt;:3868")
// 设置多流
conn.Set SCTPNodeAddresses(sctp.SCTP_PEER_ADDR_CHANGE, ...)
// 发送指定 stream 的消息
conn.WriteToSCTP([]byte("hello"), &sctp.SCTPAddr{
PeerAddr: net.SCTPAddr{IPAddrs: peerIPs},
Stream: 0, // 选择 stream 0
})
```
> [!tip] SCTP 的生态局限
> 虽然技术层面很优秀,但因为运营商部署迟缓和中间设备(NAT/防火墙)对 SCTP 的不兼容,其普及度远不及 TCP。但在电信行业仍是事实标准。
## UDP 编程示例
```go
package main
import (
"net"
"fmt"
)
func main() {
// 服务端
addr, _ := net.ResolveUDPAddr("udp4", ":8080")
conn, _ := net.ListenUDP("udp", addr)
defer conn.Close()
buf := make([]byte, 4096)
n, remote, _ := conn.ReadFromUDP(buf)
fmt.Printf("Received %d bytes from %v\n", n, remote)
// 客户端 — 注意每条 Write 是一个独立的 UDP 数据报
client, _ := net.DialUDP("udp", nil, addr)
client.Write([]byte("Hello UDP"))
}
```
## 关联笔记
- [[hhs/NETWORK/TCP段结构与状态机]] — UDP 缺少的连接管理功能
- [[hhs/NETWORK/DNS原理与优化]] — DNS 主要使用 UDP port 53
- [[hhs/NETWORK/HTTPS与TLS握手]] — TLS 也有 over UDP 的版本: DTLS