跳到主要内容

WebRTC 信令、ICE、STUN 与 TURN

📡 WebRTC 实现浏览器和移动端的实时音视频。信令、NAT 穿越和备用服务是建立连接的三大主题。

连接建立流程

呼叫方 A 信令服务器 被叫方 B
| | |
1. 创建 Offer SDP → 转发 Offer → 接收 Offer
2. 创建 Answer SDP
3. 接收 Answer ← 转发 Answer ← 发送 Answer
4. 交换 ICE Candidate ↔ ↔ ↔ ↔ 交换 ICE
5. P2P 连接建立完成

主要概念

SDP(会话描述协议)

描述媒体能力、编解码格式、网络信息等:

v=0
o=- 1234567890 1234567890 IN IP4 0.0.0.0
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104
a=rtpmap:111 opus/48000/2
a=ice-ufrag:xxxx
a=ice-pwd:yyyyyyyy

ICE(交互式连接建立)

Candidate 类型(优先级递减):
1. Host:本地网络的 IP(最快)
2. Server-Reflexive:通过 STUN 获取的公网 IP
3. Relay:通过 TURN 转发(最慢,但可靠)

STUN vs TURN

协议作用带宽占用
STUN迎取公网 IP,发现 NAT 类型不占用
TURN媒体流中继转发占用客户端全量媒体

代码示例

// 创建 PeerConnection
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:turn.example.com',
username: 'user',
credential: 'password'
}
]
})

// 添加本地流
stream.getTracks().forEach(track => pc.addTrack(track, stream))

// 监听 ICE Candidate
pc.onicecandidate = ({ candidate }) => {
if (candidate) signaling.send({ type: 'candidate', candidate })
}

// 创建 Offer
const offer = await pc.createOffer()
await pc.setLocalDescription(offer)
signaling.send({ type: 'offer', sdp: offer })

// 接收远端流
pc.ontrack = ({ streams }) => {
remoteVideo.srcObject = streams[0]
}

弱网优化

// 配置编码参数
const sender = pc.getSenders().find(s => s.track.kind === 'video')
await sender.setParameters({
encodings: [{
rid: 'high',
maxBitrate: 1200000, // 1.2Mbps
scaleResolutionDownBy: 1
}]
})

// 监听 连接质量
const stats = await pc.getStats()
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'video') {
console.log('packetsLost:', report.packetsLost)
console.log('jitter:', report.jitter)
}
})

常见误区

  • ICE 失败后没有回落到 TURN,导致某些 NAT 类型下完全无法建连
  • 信令服务器没有处理断线重连,连接才能越稳定
  • 编解码参数没有根据网络状况动态调整