4、RTCPeerConnection基础:建立点对点连接、ICE框架与STUN/TURN服务器、SDP协议简介
好,咱们今天聊点硬核的。RTCPeerConnection,这是WebRTC里最核心的一个对象。说白了,它就是帮你完成“两个浏览器之间直接传数据”的那个关键角色。我刚开始接触WebRTC的时候,觉得这东西挺玄乎的——两个浏览器怎么就能直接连上呢?中间又没有服务器转发。后来深入一看,其实底层逻辑并不复杂,就是几个协议在配合工作。
今天这一章,我会带你拆解三个核心概念:点对点连接的建立流程、ICE框架与STUN/TURN服务器、以及SDP协议。嗯,这三个东西你搞懂了,RTCPeerConnection就算入门了。
4.1 点对点连接:到底是怎么“点对点”的?
先问一个问题:两个浏览器在不同的局域网里,怎么找到对方?
你可能会说,用IP地址啊。但现实是,大多数设备都在NAT(网络地址转换)后面,你的电脑拿到的其实是一个内网IP,比如192.168.1.x。外网根本访问不到你。那怎么办?
这就是RTCPeerConnection要解决的核心问题。它不会直接去连,而是通过一个“信令服务器”先交换一些元数据,然后双方各自尝试找到一条可达的路径。
我个人习惯把整个过程分成三步:
- 交换SDP:双方先告诉对方“我支持什么编解码器、我的网络情况如何”。
- 收集ICE候选:双方各自尝试找出自己能用的IP和端口。
- 连通性检查:双方互相尝试连接,直到找到一条能用的路。
你看,其实不复杂。但每一步背后都有协议在支撑。
核心要点:RTCPeerConnection不负责传输信令数据(比如SDP),它只负责媒体数据的传输。信令的交换需要你自己实现,或者用第三方库。
4.2 ICE框架:让连接“穿墙而过”
ICE,全称Interactive Connectivity Establishment。翻译过来就是“交互式连接建立”。名字挺唬人,其实它的任务就一个:找到一条能让两个端点通信的路径。
我在项目中遇到过一个问题:两个用户都在公司内网,按理说应该能直连,但就是连不上。后来发现是防火墙把某些端口封了。ICE这时候就派上用场了——它会尝试多种路径,直到找到一条通的。
ICE的候选类型有三种:
| 候选类型 | 说明 | 典型场景 |
|---|---|---|
| host | 本机IP地址(内网) | 同一局域网内直连 |
| srflx | 经过STUN服务器反射后的公网IP | 普通家庭网络 |
| relay | 通过TURN服务器中转的地址 | 对称NAT或防火墙严格的环境 |
ICE会按照优先级从高到低尝试:先试host,不行再试srflx,最后才用relay。为什么relay放最后?因为TURN服务器要消耗带宽和计算资源,说白了就是费钱。
避坑指南:我曾经在生产环境中只配了STUN服务器,结果有大约5%的用户连不上。后来加了TURN服务器,问题就解决了。所以,如果你的应用面向公网用户,建议至少配一个TURN服务器兜底。
4.3 STUN与TURN服务器:一个指路,一个带路
STUN和TURN,这两个名字容易搞混。我简单给你区分一下:
- STUN(Session Traversal Utilities for NAT):帮你查出你的公网IP和端口。它不转发媒体数据,只做“地址查询”。
- TURN(Traversal Using Relays around NAT):当直连走不通时,它作为中继服务器,帮你转发媒体数据。它既做地址查询,也做数据转发。
你想想看,STUN就像你问路人“我现在在哪儿?”,TURN就像你打不到车,路人直接开车送你过去。两者定位完全不同。
在实际开发中,STUN服务器通常是免费的(比如Google提供的 stun:stun.l.google.com:19302),而TURN服务器一般需要自己搭建或购买云服务。
注意:TURN服务器的带宽成本不低。如果你们的应用是音视频通话,每小时可能消耗几百MB甚至上GB的流量。建议只在必要时启用TURN,或者限制中继模式的使用。
4.4 SDP协议简介:连接双方的“自我介绍”
SDP,全称Session Description Protocol。它不是一个传输协议,而是一个描述格式。用来告诉对方:我支持哪些音视频编解码器、我的网络地址是什么、我想要用什么方式传输。
我刚开始看SDP的时候,觉得它像一堆乱码。后来发现,其实结构很清晰。一个典型的SDP长这样:
v=0
o=- 1234567890 1234567890 IN IP4 192.168.1.10
s=-
t=0 0
m=audio 49170 RTP/AVP 96
a=rtpmap:96 OPUS/48000/2
a=fmtp:96 minptime=10; useinbandfec=1
c=IN IP4 192.168.1.10
a=candidate:1 1 UDP 2122252543 192.168.1.10 49170 typ host
别怕,我带你拆一下:
v=0:SDP版本号,目前就是0。o=:会话的发起者信息,包括ID、版本、地址类型等。m=:媒体描述,比如音频(audio)或视频(video),以及端口和传输协议。a=:属性行,用来描述编解码器、参数等。c=:连接信息,通常是IP地址。a=candidate:ICE候选地址,就是前面说的host/srflx/relay。
在WebRTC中,SDP的交换是通过信令服务器完成的。一端调用 createOffer() 生成SDP,另一端调用 createAnswer() 生成应答SDP。然后双方通过 setLocalDescription() 和 setRemoteDescription() 把对方的SDP设置进去。
关键点:SDP里包含了ICE候选信息。但ICE候选的收集是异步的,所以通常会在 onicecandidate 事件中逐步发送候选,而不是一次性发完。
4.5 整体流程:一张图看懂
说了这么多,咱们用一张图把整个流程串起来。下面这张SVG图展示了从信令交换到连接建立的全过程:
这张图里,我特意把信令服务器放在中间。你注意看,SDP和ICE候选的交换都经过信令服务器,但最终的媒体流是直接点对点的(图中紫色箭头)。这就是WebRTC的精髓:信令走服务器,媒体走P2P。
4.6 代码示例:一个最简单的RTCPeerConnection
光说不练假把式。咱们写一个最简版的RTCPeerConnection创建流程。假设你已经有了信令服务器(比如WebSocket),这里只展示核心逻辑:
// 发起方
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'turn:your-turn-server.com:3478', username: 'user', credential: 'pass' }
]
});
// 创建Offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// 通过信令服务器发送offer
signalServer.send({ type: 'offer', sdp: offer.sdp });
// 监听ICE候选
pc.onicecandidate = (event) => {
if (event.candidate) {
signalServer.send({ type: 'candidate', candidate: event.candidate });
}
};
// 应答方
const pc2 = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'turn:your-turn-server.com:3478', username: 'user', credential: 'pass' }
]
});
// 收到Offer后创建Answer
signalServer.onmessage = async (msg) => {
if (msg.type === 'offer') {
await pc2.setRemoteDescription(new RTCSessionDescription(msg));
const answer = await pc2.createAnswer();
await pc2.setLocalDescription(answer);
signalServer.send({ type: 'answer', sdp: answer.sdp });
}
};
这段代码虽然简单,但已经涵盖了核心流程。你注意看 iceServers 的配置,我习惯把STUN和TURN都配上去,这样兼容性最好。
小技巧:在实际项目中,ICE候选的发送不要用 onicecandidate 一个个发,可以用 onicecandidate 收集完所有候选后一次性发送,减少信令交互次数。不过要注意,收集过程可能比较慢,需要设置超时机制。
4.7 常见问题与避坑
最后,分享几个我踩过的坑:
- SDP交换顺序搞反了:必须先
setLocalDescription,再发送SDP。否则ICE候选可能不完整。 - 忘记处理ICE连接状态变化:建议监听
oniceconnectionstatechange,当状态变成disconnected或failed时做重连处理。 - TURN服务器配置错误:我曾经把TURN的端口写成了3478(默认),但实际用的是5349(TLS)。结果一直连不上,排查了半天。
嗯,这一章的内容就到这里。RTCPeerConnection的基础你已经有数了。下一章我们会深入SDP的细节,看看里面那些参数到底是什么意思。