信令交换是webrtc通话建立的必要步骤,必须通过websocket等信令通道在getusermedia前完成连接并全程在线,用于可靠传递sdp offer/answer和ice候选,否则连接必然失败。

信令交换不是可选步骤,而是 WebRTC 通话必然失败的根源——没它,offer 和 answer 根本送不到对方手里。
WebSocket 连接必须在 getUserMedia 之前建立
很多开发者先调用 navigator.mediaDevices.getUserMedia 拿流,再连 WebSocket,结果一挂断就发现信令通道已断、ICE 候选发不出去。这是因为媒体流获取可能耗时(尤其首次授权),而信令通道需全程在线才能接收/转发 SDP 和 candidate。
- 页面加载完成即初始化
new WebSocket('ws://your-signal-server'),并监听onopen确保连接就绪 - 所有信令消息统一走 JSON 格式,例如:
{ "type": "offer", "sdp": "...", "from": "userA" } - 避免在
onmessage中直接操作 DOM 或调用异步 API(如setRemoteDescription)前未检查peerConnection是否已创建 - 连接断开时,必须主动关闭
peerConnection并停止本地流:stream.getTracks().forEach(t => t.stop())
SDP offer/answer 必须严格按顺序处理
WebRTC 协商不是“谁先发谁是主叫”,而是由状态驱动:一方发 offer,另一方必须先 setRemoteDescription(offer),再 createAnswer(),最后 setLocalDescription(answer)。错一步,onconnectionstatechange 就卡在 "connecting"。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 发起方:调用
createOffer()→setLocalDescription(offer)→ 发送offer到对端 - 接收方:收到
offer后立即setRemoteDescription(offer)→createAnswer()→setLocalDescription(answer)→ 发送answer - 双方都必须监听
onicecandidate,且只在event.candidate非 null 时才通过 WebSocket 转发,否则会传空 candidate 导致连接失败 - 不要手动修改 SDP 字符串(比如删掉 a=rtcp-fb 行),现代浏览器对 SDP 兼容性敏感,微小改动可能让 VP9/AV1 编码协商失败
ICE 候选必须带完整字段,且不能漏发延迟候选
STUN/TURN 服务器返回的 ICE 候选不只有一条,尤其在多网卡或 IPv6 环境下,onicecandidate 可能触发多次。漏掉任意一条,就可能失去唯一可达路径。
- 候选消息结构应为:
{ "type": "candidate", "candidate": "candidate:...", "sdpMid": "...", "sdpMLineIndex": 0 }——sdpMid和sdpMLineIndex必须原样透传,否则addIceCandidate()会报"InvalidAccessError" - 接收方收到 candidate 后,必须立刻调用
peerConnection.addIceCandidate(candidate),不要缓存或合并 - 某些网络环境下(如企业 NAT),candidate 会在
setLocalDescription后几秒才陆续到达,需确保 WebSocket 连接在这段时间内持续可用 - 若使用 TURN,务必在
iceServers中同时配置 STUN + TURN,并验证 TURN 凭据是否过期(常见于 Spring Boot 后端未刷新 credential)
信令消息类型必须显式区分,避免 type 字段歧义
用字符串 "offer"、"answer"、"candidate" 做 type 是底线,但仅靠这个不够。多人房间或重连场景下,容易把 A 发给 B 的 candidate 错发给 C。
- 每条信令必须携带发送方 ID(如
from: "user-abc123")和目标 ID(to: "user-def456"),服务端做精准路由 - 不建议用广播模式推送所有信令,尤其
candidate—— 它只对当前会话双方有效,发错对象会导致addIceCandidate失败且无提示 - 客户端收到非本会话的信令(比如房间号不匹配),应直接丢弃,不要尝试解析或调用 WebRTC API
- 关键动作加日志:在
setLocalDescription前后打点,配合peerConnection.signalingState判断是否卡在"have-local-offer"等中间态
最常被忽略的是:信令服务器不保证消息顺序,WebSocket 本身也不提供排序保障。即便你按顺序发了 offer → candidate → answer,网络抖动也可能导致 answer 先到。所以客户端必须做好状态机兜底,比如收到 answer 时检查是否已设置 remote offer,否则暂存等待。










