WebRTC 02:信令、SDP、ICE/STUN/TURN 解析
目标:以端到端视角梳理一次连接从“发起”到“可用”的完整链路,深入讲清信令、SDP、Offer/Answer、ICE 候选、STUN 与 TURN 的职责与交互,并给出可落地的配置与代码骨架。
一、阅读地图
- 全流程概览:
Signaling → SDP(Offer/Answer) → ICE(候选收集/交换/检测) → DTLS → SRTP/SCTP。 - 关键产物:
SDP文本、ICE候选、DTLS密钥,最终形成媒体与数据的安全通道。 - 工具与观测:
chrome://webrtc-internals、pc.getStats()、浏览器控制台日志、服务端信令日志。
二、媒体捕获与 API 关系图谱
- 媒体与轨:
MediaStream包含多个MediaStreamTrack(音频/视频)。 - 连接对象:
RTCPeerConnection负责协商、连通、加密与传输。 - 收发器:
RTCRtpTransceiver统一收发语义(direction: sendrecv/sendonly/recvonly/inactive)。 - 轨与收发器关系:
addTrack绑定到发送端;addTransceiver更灵活地控制媒体与编码层(Simulcast/SVC)。 - 数据通道:
RTCDataChannel由SCTP承载,支持可靠/不可靠、有序/无序。
三、JSEP 与 SDP 结构深入
- JSEP(RFC 8829):浏览器如何使用 SDP 的规范,定义了 Offer/Answer 时机与字段。
- Unified Plan:现代浏览器的 SDP 模型,每个轨对应一个
m=行;历史的 Plan B 已废弃。 - SDP 关键行语义
v/o/s/t:版本/源/会话名/时间。m=:媒体行(audio/video/application)。c=:连接信息(通常为占位0.0.0.0)。a=rtpmap/fmtp:编解码映射与参数。a=setup/fingerprint:DTLS握手与证书指纹。a=ice-ufrag/ice-pwd:ICE 身份信息。a=msid/mid/bundle:轨标识、媒体标识与多媒体同路优化。
示例(节选,含音视频与安全/ICE 字段)
v=0
o=- 46117326 2 IN IP4 127.0.0.1
s=-
t=0 0
m=audio 9 UDP/TLS/RTP/SAVPF 111
c=IN IP4 0.0.0.0
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1
a=setup:actpass
a=ice-ufrag:abc123
a=ice-pwd:def456
a=fingerprint:sha-256 8A:...:FF
a=mid:0
a=msid:stream1 trackA
m=video 9 UDP/TLS/RTP/SAVPF 96 97
c=IN IP4 0.0.0.0
a=rtpmap:96 VP8/90000
a=rtpmap:97 H264/90000
a=sendrecv
a=mid:1
a=msid:stream1 trackV
a=group:BUNDLE 0 1
四、ICE 高级主题:角色、优先级与提名
- 角色分工:
controlling与controlled,决定谁进行最终提名(USE-CANDIDATE)。 - ICE-Lite 与 ICE-Full:服务端(如 SFU)常用
ICE-Lite;浏览器为ICE-Full。 - 候选优先级:
host > srflx > relay(通常),实际排序还受网络代价与组件影响。 - 提名策略:常见为
aggressive或regular提名,确保最佳候选对被选中并稳定。 - Trickle ICE:候选边收集边发送,结合早期连通性检测缩短建链时间。
五、STUN 实务:反射与可达性
- 职责:返回
server-reflexive地址以辅助直连,常用端口3478/udp。 - 局限:对称 NAT 下直连成功率低;需 TURN 回退。
- 实务建议:多区域/就近 STUN,减少跨洲延迟与丢包。
六、TURN 实务:中继、握手与成本
- 握手流程(简化):
Allocate → CreatePermission → ChannelBind/Connect。 - 传输类型:
UDP/TCP/TLS(5349),受企业网络与防火墙策略影响。 - 凭证:建议短时密钥与 HMAC 派发,避免长期凭证泄漏风险。
- 成本考量:TURN 需转发全量媒体,带宽成本高;优先直连,失败再回退 TURN。
七、信令设计:消息模型与可靠性
- 基本消息:
join/leave/offer/answer/ice-candidate/renegotiate/bye。 - 路由字段:
roomId/from/to,可扩展sessionId/traceId便于观测。 - 可靠性:消息按序与去重;候选批量与背压控制;断线重连与重试策略。
八、代码骨架:含 Trickle ICE 与 TURN 回退
三、信令:你定义的“消息高速路”
- 目的:交换协商所需的文本数据(
SDP)与网络候选(ICE candidate),并承载会话控制(房间、加入、离开、重协商等)。 - 载体:常见选择为
WebSocket,也可用HTTP(S)轮询/SSE、MQTT 等。WebRTC 不规定协议与格式。 - 消息模型示例
{
"type": "offer",
"roomId": "r1",
"from": "uA",
"to": "uB",
"sdp": "v=0\no=- 46117326 2 IN IP4 127.0.0.1\ns=-\nt=0 0\nm=audio 9 UDP/TLS/RTP/SAVPF 111\na=rtpmap:111 opus/48000/2\na=sendrecv\n..."
}
- 设计要点
- 明确路由字段:
roomId/from/to。 - 允许
Trickle ICE:候选一产生就发送,缩短建链时间。 - 保留
renegotiate/bye等会话指令,支持后续增删轨与重启 ICE。
- 明确路由字段:
四、SDP:文本会话描述与 Offer/Answer
- 定义:
SDP(RFC 8866)是纯文本,描述会话、媒体、编解码与扩展属性。浏览器遵循JSEP(RFC 8829)。 - 典型结构
v=0
o=- 46117326 2 IN IP4 127.0.0.1
s=-
t=0 0
m=audio 9 UDP/TLS/RTP/SAVPF 111
c=IN IP4 0.0.0.0
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1
a=setup:actpass
a=ice-ufrag:abc123
a=ice-pwd:def456
a=fingerprint:sha-256 8A:...:FF
m=video 9 UDP/TLS/RTP/SAVPF 96 97
c=IN IP4 0.0.0.0
a=rtpmap:96 VP8/90000
a=rtpmap:97 H264/90000
a=sendrecv
- Offer/Answer 流程
- 主叫:
createOffer → setLocalDescription(offer) → 通过信令发送 offer.sdp - 被叫:
setRemoteDescription(offer) → createAnswer → setLocalDescription(answer) → 发送 answer.sdp - 双方:
setRemoteDescription完成后,开始ICE候选收集与连通性检查。
- 主叫:
五、ICE:候选收集、交换与连通性检测
- 候选类型
host:本机网卡地址。srflx:STUN反射出的公网可达地址。relay:TURN中继分配的地址,连通性最好、成本/时延较高。
- Trickle ICE
- 候选边收集边发送,不等待全部候选收集完成;显著缩短首包时间。
- 连通性检测(Connectivity Checks)
- 双方对候选对进行
STUN binding检测,选择最佳路径并进行提名(nomination)。
- 双方对候选对进行
- 候选示例
candidate:842163049 1 udp 1677729535 192.168.1.10 54321 typ host
candidate:123456789 1 udp 1677724415 203.0.113.5 62344 typ srflx raddr 192.168.1.10 rport 54321
candidate:22334455 1 udp 1677716351 198.51.100.7 3478 typ relay raddr 0.0.0.0 rport 0
六、STUN:拿到“我在公网的样子”
- 作用:向
STUN服务器发送binding request,得到返回源地址/端口,即server-reflexive候选(srflx)。 - 要点:仅提供地址反射,不转发媒体;通常使用
UDP 3478。公网可达性受 NAT 类型与出口策略影响。
七、TURN:必要时走“中继快车道”
- 作用:当直连不可用或质量差时,向
TURN服务器申请分配relay候选,由其转发数据。 - 交互(简化):
Allocate → CreatePermission → ChannelBind/Connect;支持UDP/TCP/TLS,常见端口3478/5349。 - 凭证策略:生产建议短时密钥(如
turn:username@timestamp+HMAC),避免长期明文凭证泄露。 - 成本与策略:尽量优先直连,失败再回退
TURN;必要时可设置iceTransportPolicy: 'relay'验证链路。
八、代码骨架:含 Trickle ICE 与 TURN 回退
const ws = new WebSocket("wss://example.com/signal");
const pc = new RTCPeerConnection({
iceServers: [
{ urls: "stun:stun.l.google.com:19302" },
{
urls: ["turn:turn.example.com:3478", "turns:turn.example.com:5349"],
username: "u",
credential: "p",
},
],
iceCandidatePoolSize: 4,
});
function send(m) {
if (ws.readyState === 1) ws.send(JSON.stringify(m));
}
ws.onmessage = async (e) => {
const m = JSON.parse(e.data);
if (m.type === "offer") {
await pc.setRemoteDescription({ type: "offer", sdp: m.sdp });
const ans = await pc.createAnswer();
await pc.setLocalDescription(ans);
send({
type: "answer",
roomId: m.roomId,
from: m.to,
to: m.from,
sdp: ans.sdp,
});
}
if (m.type === "answer") {
await pc.setRemoteDescription({ type: "answer", sdp: m.sdp });
}
if (m.type === "ice-candidate") {
await pc.addIceCandidate(m.candidate);
}
};
pc.onicecandidate = (e) => {
if (e.candidate)
send({
type: "ice-candidate",
roomId: "r1",
from: "uA",
to: "uB",
candidate: e.candidate,
});
};
(async () => {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
});
stream.getTracks().forEach((t) => pc.addTrack(t, stream));
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
send({ type: "offer", roomId: "r1", from: "uA", to: "uB", sdp: offer.sdp });
})();
九、重协商与 ICE restart
- 何时需要:网络切换(Wi‑Fi/蜂窝)、长期弱网、路径质量显著劣化。
- 流程要点:
- 触发
pc.restartIce()或重新生成Offer携带新ICE参数。 - 通过信令交换新的
SDP,双方同步更新。
- 触发
- 与增删轨:更改媒体布局时使用重协商,保持
Transceiver与订阅一致。
示例(触发 ICE 重启)
await pc.setLocalDescription(await pc.createOffer({ iceRestart: true }));
send({ type: "offer", sdp: pc.localDescription.sdp });
十、NAT 类型与连接策略
- 常见 NAT:
Full-cone、Restricted、Port-restricted、Symmetric;越“对称”越难直连。 - 策略建议
- 总是配置
STUN与可用的TURN(含TCP/TLS)。 - 启用
Trickle ICE与合理的iceCandidatePoolSize。 - 网络切换或不稳定时执行
ICE restart(需交换新SDP)。
- 总是配置
十一、状态机、观测与排障清单
- 指标采集
const stats = await pc.getStats();
stats.forEach((r) => {
if (r.type === "candidate-pair" && r.nominated) {
const rtt = r.currentRoundTripTime;
const bitrate = r.availableOutgoingBitrate;
}
});
- 常见问题
- 只有
host候选:检查STUN可达性与代理配置。 ICE failed:补TURN并验证出口策略(3478/5349);必要时TCP/TLS。DTLS failed:校验证书与系统时间,排除中间设备拦截。- 单向媒体或无声画:检查轨道添加、
direction与订阅策略。 - 候选收敛过慢:启用 Trickle、优化候选优先级与并行拨号。
- 仅
relay候选:评估 TURN 成本,检查直连被阻原因(NAT/防火墙/代理)。
- 只有
状态机速览
signalingState:stable/have-local-offer/have-remote-offer等。iceConnectionState:new → checking → connected/completed → disconnected/failed。connectionState:new → connecting → connected → disconnected/failed/closed。
十二、最佳实践清单
- 信令采用
WebSocket,消息结构简洁清晰,保留重协商指令。 iceServers同时配置STUN与TURN,凭证短时化与按需申请。- 启用
Trickle ICE,并在弱网或变更时及时ICE restart。 - 使用
webrtc-internals与getStats做连通性与质量观测。