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-internalspc.getStats()、浏览器控制台日志、服务端信令日志。

二、媒体捕获与 API 关系图谱

  • 媒体与轨:MediaStream 包含多个 MediaStreamTrack(音频/视频)。
  • 连接对象:RTCPeerConnection 负责协商、连通、加密与传输。
  • 收发器:RTCRtpTransceiver 统一收发语义(direction: sendrecv/sendonly/recvonly/inactive)。
  • 轨与收发器关系:addTrack 绑定到发送端;addTransceiver 更灵活地控制媒体与编码层(Simulcast/SVC)。
  • 数据通道:RTCDataChannelSCTP 承载,支持可靠/不可靠、有序/无序。

三、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/fingerprintDTLS 握手与证书指纹。
    • 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 高级主题:角色、优先级与提名

  • 角色分工:controllingcontrolled,决定谁进行最终提名(USE-CANDIDATE)。
  • ICE-Lite 与 ICE-Full:服务端(如 SFU)常用 ICE-Lite;浏览器为 ICE-Full
  • 候选优先级:host > srflx > relay(通常),实际排序还受网络代价与组件影响。
  • 提名策略:常见为 aggressiveregular 提名,确保最佳候选对被选中并稳定。
  • Trickle ICE:候选边收集边发送,结合早期连通性检测缩短建链时间。

五、STUN 实务:反射与可达性

  • 职责:返回 server-reflexive 地址以辅助直连,常用端口 3478/udp
  • 局限:对称 NAT 下直连成功率低;需 TURN 回退。
  • 实务建议:多区域/就近 STUN,减少跨洲延迟与丢包。

六、TURN 实务:中继、握手与成本

  • 握手流程(简化):Allocate → CreatePermission → ChannelBind/Connect
  • 传输类型:UDP/TCP/TLS5349),受企业网络与防火墙策略影响。
  • 凭证:建议短时密钥与 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:本机网卡地址。
    • srflxSTUN 反射出的公网可达地址。
    • relayTURN 中继分配的地址,连通性最好、成本/时延较高。
  • 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-coneRestrictedPort-restrictedSymmetric;越“对称”越难直连。
  • 策略建议
    • 总是配置 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/防火墙/代理)。

状态机速览

  • signalingStatestable/have-local-offer/have-remote-offer 等。
  • iceConnectionStatenew → checking → connected/completed → disconnected/failed
  • connectionStatenew → connecting → connected → disconnected/failed/closed

十二、最佳实践清单

  • 信令采用 WebSocket,消息结构简洁清晰,保留重协商指令。
  • iceServers 同时配置 STUNTURN,凭证短时化与按需申请。
  • 启用 Trickle ICE,并在弱网或变更时及时 ICE restart
  • 使用 webrtc-internalsgetStats 做连通性与质量观测。

This article was updated on December 7, 2025