5、RTCPeerConnection入门:从零搭建P2P连接

好,咱们今天聊点硬核的。RTCPeerConnection,简称PC,是WebRTC里最核心的对象。说白了,它就是负责建立和维护两个浏览器之间点对点通信的那个“管道工”。

我个人习惯把RTCPeerConnection理解成一个“黑盒子”——你往里扔音视频流,它负责找到对方、协商参数、穿透NAT,最后把数据送过去。你不需要知道它内部怎么拧螺丝,但得知道怎么用它。

5.1 创建RTCPeerConnection对象

创建PC对象其实很简单,一行代码的事。但这里有个坑——必须传配置,尤其是ICE服务器列表。

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

const pc = new RTCPeerConnection(config);

嗯,这里要注意:iceServers 是数组,可以配多个STUN和TURN服务器。我刚开始做项目时,只配了一个STUN,结果在复杂网络环境下死活连不上。后来才明白,STUN不是万能的,遇到对称NAT它就没辙了。

小提示:Google的公共STUN服务器(stun:stun.l.google.com:19302)适合开发测试,但生产环境建议自建或购买商业TURN服务。

5.2 ICE框架:找路的过程

ICE(Interactive Connectivity Establishment)是WebRTC的“导航系统”。它的任务就是:找到一条能让两端通信的路

ICE会尝试三种路径:

  • 本地候选(Host Candidate):同一局域网内,直接连。最快,但只在局域网有效。
  • 反射候选(Server Reflexive Candidate):通过STUN服务器获取公网IP和端口。能穿透大部分NAT。
  • 中继候选(Relay Candidate):通过TURN服务器中转数据。最慢,但最可靠。

ICE会收集所有候选地址,然后排序、测试。优先级是:Host > Srflx > Relay。说白了,能直连绝不绕路。

核心理解:ICE不是一次性成功的。它会不断尝试候选对,直到找到一条可用的路径。这个过程叫“连通性检查”。

5.3 STUN/TURN服务器配置

STUN和TURN,名字像兄弟,作用完全不同。

特性 STUN TURN
作用 获取公网IP和端口 中转音视频数据
带宽消耗 极低(仅信令) 高(转发媒体流)
适用场景 大多数NAT环境 对称NAT或防火墙限制
成本 免费或极低 较高(带宽费用)

我曾经在一个项目中只配了STUN,结果用户反馈“有时候能连,有时候连不上”。排查后发现,那些连不上的用户都在企业内网,用的是对称NAT。STUN拿到的地址根本没用,必须上TURN。

避坑指南:不要在生产环境只配STUN。至少准备一个TURN服务器作为兜底。我曾经因为省这点钱,被用户骂了一周。

5.4 Offer/Answer模型与SDP协议

Offer/Answer是WebRTC的“握手协议”。流程很简单:

  1. A方创建Offer(包含A支持的音视频编码、分辨率、ICE候选等)
  2. A方通过信令通道把Offer发给B
  3. B方收到Offer,创建Answer(包含B支持的参数)
  4. B方把Answer发回给A

这个过程中交换的“参数包”就是SDP(Session Description Protocol)。SDP本质上是一段文本,描述了媒体能力、网络信息等。

// A方:创建Offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// 通过信令发送offer给B

// B方:接收Offer并创建Answer
await pc.setRemoteDescription(offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
// 通过信令发送answer给A

你想想看,这个过程像不像两个人打电话?A说“我能说中文和英文,你那边呢?”B说“我只会中文,那就用中文吧。”——这就是Offer/Answer。

个人经验:SDP里有很多字段,但初学者不用全看懂。重点关注 m=(媒体行)和 a=(属性行)。调试时打印SDP出来看看,能帮你快速定位问题。

5.5 建立P2P连接的基本流程

好了,我们把所有知识点串起来。一个完整的P2P连接流程是这样的:

  1. 创建PC对象:配好STUN/TURN服务器
  2. 添加媒体流:通过 addTrack()addTransceiver() 把本地音视频加进去
  3. 创建Offer:调用 createOffer(),然后 setLocalDescription()
  4. 信令交换:通过WebSocket或其他方式把Offer发给远端
  5. 远端处理:远端调用 setRemoteDescription(),然后创建Answer
  6. 交换Answer:把Answer发回给发起方
  7. ICE候选交换:两端通过 onicecandidate 事件收集并交换候选地址
  8. 连接建立:ICE找到可用路径,触发 oniceconnectionstatechange 事件,状态变为 connected

嗯,这里有个关键点:ICE候选的交换是异步的。不要等所有候选都收集完再发,而是每收集到一个就立刻发出去。这样可以加速连接建立。

核心流程图:
RTCPeerConnection P2P连接建立流程 发起方 (A) 接收方 (B) 1. 创建PC对象 2. 添加媒体流 3. 创建Offer Offer (SDP) 4. 接收Offer 5. 创建Answer Answer (SDP) 6. 接收Answer 7. ICE候选交换 7. ICE候选交换 ICE候选 (双向) 8. P2P连接建立成功

这个图把整个流程串起来了。你仔细看,Offer/Answer是单向的(A→B→A),而ICE候选是双向的(两边同时发)。

调试技巧:监听 oniceconnectionstatechange 事件,打印状态变化。如果卡在 checking 状态不动,大概率是STUN/TURN配置有问题。

好了,RTCPeerConnection的基本用法就这些。说白了就是三步:创建对象、交换SDP、交换ICE候选。剩下的就是监听事件、处理数据流了。

我个人建议你动手写个demo,哪怕只是两个页面之间互相ping一下。只有亲手跑通一次,你才能真正理解这些概念。


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