4、RTCPeerConnection:连接建立流程、ICE框架、STUN/TURN服务器配置、SDP协商
好,咱们今天聊点硬核的。
RTCPeerConnection,这是 WebRTC 里最核心的一个对象。说白了,它就是负责在两个浏览器之间建立一条“点对点”的媒体通道。你想想看,视频流、音频流、甚至文件数据,最后都得靠它来传输。
我刚开始接触 WebRTC 的时候,觉得这东西就是个黑盒。new 一个对象,然后 addTrack,再 setLocalDescription,好像就完事了。但实际一跑,发现连不上。嗯,这里面的门道,比想象中多得多。
4.1 连接建立流程:从零到一
我们先捋一遍整体流程。RTCPeerConnection 的连接建立,可以拆成四个大步骤:
- 创建 PeerConnection 实例——这是起点,所有后续操作都基于它。
- 添加媒体流——把本地摄像头、麦克风采集到的轨道加进去。
- SDP 协商——双方交换各自的媒体能力,达成一致。
- ICE 连接检查——找到一条能通的网络路径,建立连接。
我个人习惯把第三步和第四步看作“握手”和“找路”。握手谈的是“我能编解码什么格式”,找路谈的是“咱们怎么把数据传过去”。
核心要点:整个流程是异步的,而且依赖信令服务器来中转消息。浏览器之间不直接交换 SDP 和 ICE 候选者,必须通过你搭建的信令通道。
我在项目中遇到过一个问题:两个用户都在同一个局域网,按理说应该很快连上,但就是卡在“connecting”状态。后来发现,是信令服务器把 SDP 消息的顺序搞乱了。嗯,信令的可靠性,有时候比媒体本身还重要。
4.2 ICE 框架:网络世界的探路者
ICE,全称 Interactive Connectivity Establishment。名字挺长,但意思很简单:帮两个端点找到一条能通信的路。
为什么会需要 ICE?因为现实网络太复杂了。有 NAT、有防火墙、有对称路由。你想想看,你的电脑在家庭路由器后面,对方的手机在 4G 网络里,它们之间怎么直接通信?
ICE 框架的核心是收集“候选者”(candidates)。每个候选者代表一种可能的连接方式:
- 主机候选者(host)——本机网卡上的 IP 和端口。局域网内通信用这个就够了。
- 反射候选者(srflx)——通过 STUN 服务器获取的公网 IP 和端口。用于穿透简单的 NAT。
- 中继候选者(relay)——通过 TURN 服务器分配的中继地址。这是最后的保底方案。
ICE 的流程是这样的:双方各自收集候选者,然后通过信令交换给对方。接着,每一对候选者之间都会进行连通性检查。谁先通,就用谁。
我的经验:调试 ICE 问题时,打开 chrome://webrtc-internals 这个页面,能看到所有候选者的状态。我曾经靠这个页面定位过一个“候选者优先级排序错误”的问题——两个 srflx 候选者优先级一样,导致 ICE 选择了错误的路径。
下面这张图,是我自己整理的 ICE 框架核心逻辑:
4.3 STUN/TURN 服务器配置
STUN 和 TURN,这两个东西经常被放在一起说,但它们的角色完全不同。
STUN(Session Traversal Utilities for NAT):它的任务很简单——告诉你的浏览器,“你从外面看是什么 IP 和端口”。浏览器拿到这个信息后,把它作为 srflx 候选者发给对方。
TURN(Traversal Using Relays around NAT):当 STUN 搞不定的时候(比如对称 NAT 环境),TURN 就上场了。它充当一个中继,所有媒体数据都经过它转发。
配置方式很简单,在创建 RTCPeerConnection 时传入 iceServers 参数:
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);
注意:STUN 服务器是免费的,Google 提供的 stun.l.google.com:19302 可以用于开发和测试。但 TURN 服务器需要自己搭建或购买服务,因为它消耗带宽和计算资源。我曾经在生产环境中只用了一个免费 STUN,结果有 15% 的用户连不上——都是对称 NAT 环境。后来加了 TURN,问题才解决。
我建议你在配置时,至少准备两个 STUN 服务器做冗余。TURN 的话,根据用户量来评估。如果只是内部会议系统,自建一个 coturn 就够了。
4.4 SDP 协商:媒体能力的谈判桌
SDP(Session Description Protocol)不是传输协议,而是一种描述格式。它描述了“我能做什么”和“我想要什么”。
协商过程分两步:
- 发起方(Offerer)创建 Offer,包含本地的媒体信息。
- 应答方(Answerer)收到 Offer 后,创建 Answer,表示接受或调整。
一个典型的 SDP 长这样:
v=0
o=- 1234567890 2 IN IP4 127.0.0.1
s=-
t=0 0
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98
c=IN IP4 0.0.0.0
a=rtpmap:96 H264/90000
a=rtpmap:97 VP8/90000
a=rtpmap:98 VP9/90000
a=sendrecv
这里面最关键的是 m= 行和 a=rtpmap 行。它们告诉对方:我支持 H264、VP8、VP9 三种视频编码,你可以选一个。
代码实现是这样的:
// 发起方
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// 通过信令发送 offer 给对方
// 应答方
await pc.setRemoteDescription(offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
// 通过信令发送 answer 给对方
// 发起方收到 answer
await pc.setRemoteDescription(answer);
避坑指南:我曾经在 setLocalDescription 之前就调了 createOffer,结果 SDP 里没有 ice-ufrag 和 ice-pwd 字段。嗯,顺序很重要。先 createOffer,再 setLocalDescription,这个顺序不能乱。
还有一个细节:SDP 协商过程中,双方可能会多次交换 Offer 和 Answer。比如一方中途开启了屏幕共享,就需要重新协商。这叫“后续协商”(后续 Offer/Answer)。
好了,RTCPeerConnection 的核心内容就这些。从创建实例到 ICE 找路,再到 SDP 谈能力,每一步都有坑,但也都有解法。你动手写一遍代码,跑通一个简单的点对点视频通话,这些概念就全活了。
公众号:蓝海资料掘金营,微信deep3321