--- 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
内置 ACK+重传+流控"] U --> DTLS["DTLS
TLS over UDP"] U --> RTP["RTP/RTCP
媒体流+反馈"] U --> Custom["自定义可靠协议
如 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", "<local>:3868", "<peer>: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