裸UDP无法支撑高并发音视频传输,因其缺乏序列控制、丢包重传、分片重组和流量整形等关键能力;必须在UDP之上构建具备ARQ机制、滑动窗口、分片重组、心跳保活的可靠传输逻辑。

直接用 net.ListenUDP 启动服务不能支撑高并发音视频传输——它只是裸 UDP 收发入口,没有序列控制、丢包重传、分片重组、流量整形等关键能力。真正在生产环境跑音视频流,必须在 UDP 之上补一层可靠、可控、可调的传输逻辑。
为什么裸 UDP 在音视频场景下会快速崩盘
UDP 本身不保证送达、不保序、不分片、不拥塞控制。在公网或弱网环境下,一个 1400 字节的音频帧被丢掉,接收端就卡顿;连续乱序 3 个视频包,解码器直接花屏;大 GOP 视频帧超 MTU 被 IP 层分片,任意一片丢失整帧报废。
- 丢包率 >1% 时,裸 UDP 的音频会出现明显断续,视频 P 帧参考链断裂
- 无滑动窗口限制发送速率,突发流量打满网卡或触发运营商限速
- 所有包共用一个
buffer和单 goroutine 处理,CPU 利用率低且无法横向扩展 - 没心跳机制,NAT 超时后连接静默失效,客户端收不到任何通知
必须自己实现的 4 个核心模块
不是“封装 UDP”,而是用 Go 构建一套轻量级 ARQ 协议栈,每个模块都要独立可测、可调、可中断:
-
包头设计:每个
[]byte开头必须含conv(会话 ID)、seq(uint32 大端)、ack(最近连续接收 seq)、flag(DATA/ACK/FRAG/HEARTBEAT),否则无法做去重、滑窗、分片对齐 -
滑动窗口 + inflight 管理:发送端用
map[uint32]*packet记录待确认包,搭配sync.Mutex;避免用全局锁,否则每发一个包都阻塞其他连接 -
ACK 策略与重传定时器:绝不用
time.Sleep;为每个 DATA 包配独立*time.Timer,超时触发重传;收到对应ack_seq立即调用timer.Stop();Timer.Reset()返回 false 时需新建 timer -
分片与重组:大帧(如 I 帧)按
MaxUDPPayload = 1400切片,每片带msg_id、frag_id、total_frags;接收端用map[uint32][]*fragment+sync.RWMutex缓存,200ms 内未收齐则丢弃整条消息
Go 并发模型的关键取舍点
goroutine 不是万能银弹,用错反而拖垮性能:
立即学习“go语言免费学习笔记(深入)”;
- 不要为每个 UDP 包起 goroutine:
ReadFromUDP之后立刻进 worker pool,避免频繁调度开销 - 接收缓冲区用
sync.RWMutex(读多写少),发送队列和 inflight map 用独立sync.Mutex,别混用同一把锁 - 心跳包走专用 goroutine,每 5 秒发一次
HEARTBEAT,连续 3 次无响应就清理conv对应的所有状态 - Packet 结构体字段全用
uint32/int64,禁用指针——方便用atomic更新时间戳或状态,避免锁竞争
容易被忽略的部署细节
本地跑通 ≠ 上线可用:
- Linux 默认
net.core.rmem_max是 212992 字节,音视频突发流量容易丢包;上线前至少调到4194304(4MB) - UDP socket 必须设置
SetReadBuffer和SetWriteBuffer,否则内核缓冲区不够用,表现就是“明明没满 CPU 却收不到包” - 不要在 NAT 后直接暴露 UDP 端口——加一层
conv校验和 timestamp 防重放,否则可能被恶意 ACK 洪泛攻击 - 真实音视频流中,RTP over UDP 是事实标准;如果你自己造协议,务必兼容
rtp_header_t的sequence和timestamp字段,否则 WebRTC 客户端根本连不上


















