10、RTCPeerConnection:创建、配置与状态监听

好,咱们进入 WebRTC 最核心的部分——RTCPeerConnection

你可以把它理解成一条「音视频管道」。没有它,两个浏览器之间就没法传数据。我刚开始接触 WebRTC 时,以为只要拿到摄像头流就能直接发出去,结果折腾半天发现——嗯,少了这个关键对象。

10.1 创建 RTCPeerConnection 对象

创建方式很简单,一行代码就行:

const pc = new RTCPeerConnection();

但别高兴太早。实际项目中,你几乎不会这样裸写。为什么呢?因为缺了配置参数。

我个人习惯在创建时就把 STUN/TURN 服务器传进去。这样后续不用再补配置,逻辑更清晰。

const config = {
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    {
      urls: 'turn:your-turn-server.com:3478',
      username: 'user',
      credential: 'pass'
    }
  ]
};

const pc = new RTCPeerConnection(config);

你想想看,如果只传空配置,WebRTC 就只能走局域网。一旦遇到 NAT 穿透,立马歇菜。

小提示:STUN 服务器是免费的,Google 那个就能用。但 TURN 服务器需要自己搭,或者买云服务。别在生产环境用免费 TURN,带宽扛不住。

10.2 配置 STUN/TURN 服务器

STUN 和 TURN 是干什么的?说白了就是帮两个浏览器「找到对方」。

  • STUN:帮你拿到自己的公网 IP 和端口。适合大部分场景。
  • TURN:当 P2P 连不上时,作为中继转发数据。兜底方案。

我在项目中遇到过一个问题:用户在内网环境,STUN 能拿到公网 IP,但就是连不上。后来发现是防火墙把 UDP 封了。这时候只能走 TURN。

配置时要注意:

const config = {
  iceServers: [
    { urls: 'stun:stun.voiparound.com' },
    {
      urls: [
        'turn:turn.voiparound.com:3478',
        'turns:turn.voiparound.com:5349'
      ],
      username: 'test',
      credential: 'test123'
    }
  ],
  iceCandidatePoolSize: 10
};

这里有个细节:iceCandidatePoolSize 可以预生成候选者。我建议设成 10 左右,能加快连接速度。

注意:TURN 的 credential 不要硬编码在前端。我曾经见过有人把密码写在 JS 里,结果被爬走了。最好通过后端接口动态获取。

10.3 连接状态监听

创建完对象,配置好服务器,接下来就是等连接建立。但你不能干等,得监听状态变化。

WebRTC 提供了几个关键事件:

事件名 触发时机 常用场景
iceconnectionstatechange ICE 连接状态变化 判断是否连通
connectionstatechange 整体连接状态变化 显示连接质量
icecandidate 发现新的候选者 收集候选者
icegatheringstatechange 候选者收集状态变化 判断收集是否完成

我最常用的是 iceconnectionstatechange

pc.oniceconnectionstatechange = () => {
  console.log('ICE 状态:', pc.iceConnectionState);

  switch (pc.iceConnectionState) {
    case 'checking':
      // 正在尝试连接
      break;
    case 'connected':
      // 连接成功
      break;
    case 'disconnected':
      // 短暂断开,可能还能恢复
      break;
    case 'failed':
      // 彻底失败,需要重建
      break;
    case 'closed':
      // 连接关闭
      break;
  }
};

嗯,这里要注意:disconnectedfailed 的区别。前者是临时断开,WebRTC 会尝试重连;后者是彻底没戏了。

我曾经踩过一个坑:用户网络波动导致 disconnected,我直接关闭了连接。结果用户骂娘了。后来改成等 5 秒再判断,如果还是 disconnected 再关。

核心经验:不要一看到 disconnected 就慌。给 WebRTC 一点时间,它自己会尝试恢复。实在不行再走重建流程。

10.4 完整的创建与监听示例

把上面这些串起来,就是一个完整的初始化流程:

async function createPeerConnection() {
  const config = {
    iceServers: [
      { urls: 'stun:stun.l.google.com:19302' },
      {
        urls: 'turn:your-turn.com:3478',
        username: 'dynamic_user',
        credential: 'dynamic_pass'
      }
    ],
    iceCandidatePoolSize: 10
  };

  const pc = new RTCPeerConnection(config);

  // 监听 ICE 候选者
  pc.onicecandidate = (event) => {
    if (event.candidate) {
      // 发送给远端
      sendCandidate(event.candidate);
    }
  };

  // 监听连接状态
  pc.oniceconnectionstatechange = () => {
    updateUI(pc.iceConnectionState);
  };

  // 监听整体连接状态
  pc.onconnectionstatechange = () => {
    console.log('连接状态:', pc.connectionState);
  };

  return pc;
}

你想想看,这个流程其实就三步:创建对象、配置服务器、监听状态。但每一步都有坑。

10.5 避坑指南

最后分享几个我踩过的坑:

  • STUN 服务器不可用:Google 的 STUN 偶尔会被墙。我建议备选 2-3 个 STUN 服务器。
  • TURN 带宽超限:TURN 流量走服务器,带宽很贵。我曾经一个月烧掉 200 刀。后来加了流量限制。
  • 状态监听遗漏:只监听 iceconnectionstatechange 不够。还要监听 connectionstatechange,后者更全面。
  • 候选者收集超时:有些网络环境收集候选者很慢。我建议加个 10 秒超时,超时后强制开始连接。
我的习惯:每次创建 RTCPeerConnection 时,都打印一份完整的配置日志。这样出问题时,能快速定位是 STUN 挂了还是 TURN 配置错了。

10.6 核心逻辑流程图

下面这张图帮你理清整个流程:

RTCPeerConnection 核心流程 创建 RTCPeerConnection 配置 STUN / TURN 服务器 监听连接状态变化 等待 ICE 连接完成 ✅ connected 🔄 disconnected ❌ failed disconnected 状态会尝试自动重连,failed 需要手动重建连接

这张图把整个流程串起来了。从创建对象开始,到配置服务器,再到监听状态,最后根据状态做不同处理。你写代码时,就按这个顺序来。

总结一下:RTCPeerConnection 是 WebRTC 的基石。创建时配好 STUN/TURN,监听好状态变化,就能应对大部分网络环境。别偷懒,该配的配全,该监听的监听全。

公众号:蓝海资料掘金营,微信deep3321