WebRTC 架构基础:P2P通信模型、信令服务器、STUN/TURN协议、ICE框架
WebRTC 的核心,说白了就是让两个浏览器直接对话。不需要中间服务器转发数据,这就是 P2P(点对点)通信。但现实网络远比想象复杂——NAT、防火墙、对称路由,哪个都能让直连梦碎。这一章,我们就来拆解 WebRTC 如何绕过这些障碍。
P2P 通信模型:理想与现实
P2P 的初衷很简单:A 和 B 直接建立连接,数据不经过第三方。延迟低、带宽省、成本也低。但现实是,大多数设备躲在 NAT 后面,没有公网 IP。A 想找 B,连 B 在哪儿都不知道。
我早期做的一个项目,两个客户端都在公司内网,结果死活连不上。后来才发现,公司出口做了对称 NAT,常规打洞方式根本无效。嗯,这就是 P2P 的痛点——理想很丰满,现实很骨感。
核心要点:WebRTC 的 P2P 不是“一定能直连”,而是“尽力直连”。直连失败时,会回退到 TURN 中继。
信令服务器:连接的“媒人”
WebRTC 没有定义信令协议,只定义了信令需要交换的信息。信令服务器就是那个“媒人”,帮双方交换 SDP(会话描述协议)和 ICE 候选。
SDP 里包含什么?媒体类型、编解码器、IP 端口、加密参数。说白了,就是告诉对方“我支持什么格式,我在哪儿等你”。
信令流程通常是这样:
- A 创建 Offer SDP,发给信令服务器
- 信令服务器转发给 B
- B 创建 Answer SDP,发回给 A
- 双方开始交换 ICE 候选
个人建议:信令传输用 WebSocket 最省事。别用 HTTP 轮询,延迟高还浪费带宽。我习惯在信令里加个 roomId 字段,方便多人会议场景。
STUN 协议:帮设备找到自己的“门牌号”
STUN(Session Traversal Utilities for NAT)的作用很简单:客户端向 STUN 服务器发个请求,服务器返回“我看到你的公网 IP 和端口是 xxx”。这样设备就知道自己在 NAT 外面的地址了。
但 STUN 有个致命局限——它只对锥形 NAT 有效。对称 NAT 下,STUN 返回的地址映射只对 STUN 服务器本身有效,换一个目标 IP,端口就变了。
我曾经踩过的坑:在对称 NAT 环境下,STUN 返回的地址看起来没问题,但 Peer 连接时一直超时。排查了半天才发现,STUN 给的映射只对 STUN 服务器有效,对 Peer 根本不通。这时候必须上 TURN。
TURN 协议:最后的保底方案
TURN(Traversal Using Relays around NAT)就是中继模式。客户端把数据发给 TURN 服务器,服务器再转发给对端。延迟高、带宽贵,但保证能通。
什么时候必须用 TURN?
- 对称 NAT 环境
- 防火墙限制 UDP
- 企业内网策略严格
TURN 的代价不小。我算过一笔账:一个 720p 视频流,TURN 中继每月带宽成本大约是直连的 3-5 倍。所以,能用 STUN 就别开 TURN。
| 协议 | 作用 | 适用场景 | 成本 |
|---|---|---|---|
| STUN | 获取公网映射地址 | 锥形 NAT | 低 |
| TURN | 中继转发数据 | 对称 NAT / 防火墙 | 高 |
ICE 框架:智能选路引擎
ICE(Interactive Connectivity Establishment)是 WebRTC 的“大脑”。它收集所有可能的候选地址(本地、STUN、TURN),然后按优先级排序,逐一尝试连接。
ICE 的工作流程:
- 收集候选:本地地址、STUN 反射地址、TURN 中继地址
- 排序:按优先级(通常本地 > STUN > TURN)
- 连通性检查:发送 STUN binding 请求,验证对端是否可达
- 选出最佳路径:第一个成功的候选对就是最终连接
关键点:ICE 不是“选最快的”,而是“选第一个能通的”。所以候选排序很重要。我习惯把 TURN 候选优先级设低,避免浪费带宽。
整体架构图
下面这张图展示了 WebRTC 建立连接的全流程。从信令交换到 ICE 选路,每一步都不可或缺。
ICE 候选类型对比
ICE 候选有三种类型,优先级从高到低:
| 候选类型 | 来源 | 优先级 | 典型延迟 |
|---|---|---|---|
| host | 本地网卡 | 最高 | <1ms |
| srflx | STUN 反射 | 中 | 5-20ms |
| relay | TURN 中继 | 最低 | 50-200ms |
避坑指南:我曾经遇到一个 case,ICE 一直选 relay 候选,导致延迟很高。排查发现是 srflx 候选的优先级设错了。检查一下 iceCandidatePoolSize 和候选排序逻辑,能省不少事。
ICE 状态机
ICE 连接不是一蹴而就的。它有一个清晰的状态机:
- checking:正在尝试候选对
- connected:至少一个候选对通了
- completed:所有候选对检查完毕
- failed:所有候选对都失败
- disconnected:连接中断
我习惯在 oniceconnectionstatechange 回调里打日志,方便排查问题。特别是 failed 状态,基本就是 NAT 穿透失败,得检查 TURN 配置。
一句话总结:信令负责“握手”,STUN 负责“找门牌”,TURN 负责“当快递员”,ICE 负责“选最快路线”。四者缺一不可。