3、WebRTC开发环境搭建:浏览器兼容性检查、本地信令服务器搭建、第一个P2P连接

好,咱们正式开始动手了。前面聊了那么多WebRTC的概念和架构,说实话,光看不练容易犯困。这一章我带你把开发环境搭起来,然后跑通第一个P2P连接。嗯,这一步走通了,后面就好办了。

3.1 浏览器兼容性检查

WebRTC虽然已经是个成熟技术,但不同浏览器对它的支持程度还是有差异的。我几年前在一个跨平台项目中就吃过这个亏——Chrome上跑得好好的,换到Safari直接黑屏。所以,第一步先检查环境。

3.1.1 哪些浏览器支持?

目前主流浏览器对WebRTC的支持情况,我整理了一张表:

浏览器 支持版本 备注
Chrome 28+ 支持最完整,推荐开发用
Firefox 22+ 兼容性不错,偶有API差异
Safari 11+ 早期版本坑多,建议用最新版
Edge 79+(Chromium版) 老Edge别用了,API不完整
Opera 20+ 基本和Chrome一致

我个人习惯用Chrome做主力开发,调试工具最顺手。但最终上线前,一定要在Safari和Firefox上过一遍。

3.1.2 快速检测脚本

写一个简单的检测函数,几行代码就能判断当前浏览器是否支持WebRTC:

function checkWebRTCSupport() {
  const result = {
    supported: false,
    details: {}
  };

  // 检查核心API
  result.details.RTCPeerConnection = !!(
    window.RTCPeerConnection ||
    window.webkitRTCPeerConnection ||
    window.mozRTCPeerConnection
  );

  // 检查媒体流
  result.details.getUserMedia = !!(
    navigator.mediaDevices?.getUserMedia ||
    navigator.getUserMedia ||
    navigator.webkitGetUserMedia ||
    navigator.mozGetUserMedia
  );

  // 检查数据通道
  result.details.RTCDataChannel = !!(
    window.RTCDataChannel ||
    window.webkitRTCDataChannel
  );

  result.supported = result.details.RTCPeerConnection &&
                     result.details.getUserMedia;

  return result;
}

console.log('WebRTC支持情况:', checkWebRTCSupport());

把这段代码扔到浏览器控制台里跑一下,如果 supportedtrue,恭喜,环境没问题。

小提示: 如果你用的是Chrome,建议打开 chrome://webrtc-internals 这个页面。里面能看到所有WebRTC连接的内部状态,排查问题特别好用。我在项目里遇到连接失败时,第一件事就是去这里看日志。

3.2 本地信令服务器搭建

WebRTC的P2P连接需要信令服务器来交换元数据。说白了,就是两个浏览器之间需要先打个招呼:「嘿,我在这里,这是我的网络信息,咱们连一下吧。」

这个信令服务器本身不传音视频数据,所以用Node.js搭一个轻量的就够了。

3.2.1 初始化项目

先确保你装了Node.js(版本12以上就行)。然后新建一个文件夹:

mkdir webrtc-signaling-server
cd webrtc-signaling-server
npm init -y
npm install ws

ws 是一个轻量级的WebSocket库,用来做信令通道正合适。我试过用Socket.IO,但说实话,对于学习阶段,ws 更纯粹,能让你看清信令的本质。

3.2.2 信令服务器代码

创建一个 server.js 文件:

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

// 存储所有连接的客户端
const clients = new Set();

wss.on('connection', (ws) => {
  console.log('新客户端连接');
  clients.add(ws);

  // 收到消息后广播给其他客户端
  ws.on('message', (message) => {
    console.log('收到消息:', message.toString());

    // 转发给其他所有客户端
    clients.forEach((client) => {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(message.toString());
      }
    });
  });

  // 客户端断开连接
  ws.on('close', () => {
    console.log('客户端断开');
    clients.delete(ws);
  });
});

console.log('信令服务器已启动,监听端口 8080');

这个服务器做的事情很简单:收到一个客户端的消息,就转发给其他所有客户端。这就是信令的核心——消息转发。

注意: 这个实现只适合本地学习。生产环境需要处理房间管理、用户身份验证、消息去重等问题。我曾经在生产环境中因为没做消息去重,导致两个客户端互相转发同一份SDP,循环了好几次才超时断开。

启动服务器:

node server.js

看到「信令服务器已启动,监听端口 8080」就说明成功了。

3.3 第一个P2P连接

好,环境搭好了,信令服务器也跑起来了。现在我们来写两个页面,让它们建立P2P连接。

3.3.1 整体流程

先看一张流程图,理清整个交互过程:

WebRTC P2P连接建立流程 浏览器 A 信令服务器 浏览器 B ① 创建 Offer ② 发送 Offer ③ 转发 Offer ④ 创建 Answer ⑤ 发送 Answer ⑥ 转发 Answer ICE 候选者交换(双向) ✅ P2P 连接建立成功 浏览器A 信令服务器 浏览器B ICE候选 P2P直连

这张图把整个流程拆成了6个步骤加上ICE候选交换。说白了就是:A先发个「邀请」(Offer),B收到后回个「接受」(Answer),然后两人互相交换网络地址信息(ICE候选),最后直接连上。

3.3.2 客户端代码

创建一个 client.html 文件:

<!DOCTYPE html>
<html>
<head>
  <title>WebRTC P2P连接演示</title>
</head>
<body>
  <h2>WebRTC P2P连接演示</h2>
  <div id="status">状态:未连接</div>
  <button id="connectBtn">建立连接</button>
  <div id="log"></div>

  <script>
    const statusDiv = document.getElementById('status');
    const logDiv = document.getElementById('log');
    const connectBtn = document.getElementById('connectBtn');

    let pc = null;
    let ws = null;

    function log(msg) {
      const p = document.createElement('p');
      p.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
      logDiv.appendChild(p);
    }

    function createPeerConnection() {
      // 创建RTCPeerConnection
      pc = new RTCPeerConnection({
        iceServers: [
          { urls: 'stun:stun.l.google.com:19302' }
        ]
      });

      // 监听ICE候选事件
      pc.onicecandidate = (event) => {
        if (event.candidate) {
          ws.send(JSON.stringify({
            type: 'candidate',
            candidate: event.candidate
          }));
        }
      };

      // 监听连接状态变化
      pc.onconnectionstatechange = () => {
        log(`连接状态变化: ${pc.connectionState}`);
        if (pc.connectionState === 'connected') {
          statusDiv.textContent = '状态:已连接 ✅';
        }
      };

      log('RTCPeerConnection 创建成功');
    }

    async function startConnection() {
      // 连接信令服务器
      ws = new WebSocket('ws://localhost:8080');

      ws.onopen = () => {
        log('已连接到信令服务器');
        createPeerConnection();
        connectBtn.disabled = true;
      };

      ws.onmessage = async (event) => {
        const data = JSON.parse(event.data);
        log(`收到信令消息: ${data.type}`);

        switch (data.type) {
          case 'offer':
            // 收到Offer,设置远端描述并创建Answer
            await pc.setRemoteDescription(new RTCSessionDescription(data));
            const answer = await pc.createAnswer();
            await pc.setLocalDescription(answer);
            ws.send(JSON.stringify({
              type: 'answer',
              sdp: answer.sdp
            }));
            log('已发送 Answer');
            break;

          case 'answer':
            // 收到Answer,设置远端描述
            await pc.setRemoteDescription(new RTCSessionDescription(data));
            log('已设置远端描述');
            break;

          case 'candidate':
            // 收到ICE候选
            await pc.addIceCandidate(new RTCIceCandidate(data.candidate));
            log('已添加 ICE 候选');
            break;
        }
      };

      // 创建Offer(第一个客户端执行)
      const offer = await pc.createOffer();
      await pc.setLocalDescription(offer);
      ws.send(JSON.stringify({
        type: 'offer',
        sdp: offer.sdp
      }));
      log('已发送 Offer');
    }

    connectBtn.addEventListener('click', startConnection);
  </script>
</body>
</html>

3.3.3 运行测试

打开两个浏览器标签页,都访问这个 client.html。先点第一个页面的「建立连接」,再点第二个页面的「建立连接」。你会看到:

  1. 两个页面都连上了信令服务器
  2. 第一个页面发送Offer
  3. 第二个页面收到Offer并回复Answer
  4. 双方交换ICE候选
  5. 状态变成「已连接 ✅」
成功了! 你现在已经跑通了第一个WebRTC P2P连接。虽然这个连接还没传音视频数据,但底层通道已经建立起来了。后面加媒体流就是水到渠成的事。

3.4 常见问题与避坑

我在带团队做WebRTC项目时,新人最容易在这几个地方卡住:

  • 信令服务器连不上:检查端口是否被占用,防火墙是否放行。我曾经在Windows上被防火墙坑了一下午。
  • Offer/Answer顺序搞反:记住,先有Offer才有Answer。如果两个页面同时发Offer,信令服务器会乱套。
  • ICE候选没交换成功:本地测试一般没问题,但跨网络时一定要配STUN/TURN服务器。上面代码里用的Google公共STUN服务器,局域网内够用了。
  • 浏览器控制台报错:打开开发者工具,看Console和Network面板。WebRTC的错误信息通常很明确,比如 Failed to set remote answer sdp 这种,直接搜就能找到原因。
避坑指南: 我曾经在Safari上遇到过一个问题——RTCPeerConnection 的构造函数需要加 webkit 前缀。虽然新版本Safari已经支持无前缀写法,但如果你要兼容旧设备,最好加个判断。上面检测脚本里已经处理了这种情况。

嗯,到这里,你的WebRTC开发环境就搭好了,第一个P2P连接也跑通了。后面我们会在这个基础上加音视频流、数据通道,一步步把功能做完整。


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