18、WebRTC与WebSocket混合架构:何时需要混合使用、数据分流策略、实战案例:实时协作白板
说实话,做了这么多年实时通信,我见过太多人一上来就问:「WebRTC 能不能完全替代 WebSocket?」
我的回答一直是:不能,也不该。
WebRTC 和 WebSocket 不是对手,而是搭档。就像你盖房子,WebSocket 是地基和管道,WebRTC 是高速电梯。各有各的活儿,硬要混着用反而坏事。
这一章,我就带你看看什么时候该混合用,怎么分流数据,最后用白板实战把整个流程串起来。
核心观点:WebRTC 负责低延迟、高吞吐的媒体流和实时数据通道;WebSocket 负责信令、状态同步、非实时控制指令。两者互补,缺一不可。
什么时候需要混合使用?
我总结了三类场景,只要命中其中一条,你就该考虑混合架构了:
- 需要信令通道——WebRTC 建立连接前必须交换 SDP 和 ICE 候选,WebSocket 是最优雅的信令载体。我早期用 HTTP 轮询做信令,延迟高得离谱,后来全换成 WebSocket,体验直接起飞。
- 需要可靠传输——WebRTC 的 DataChannel 默认是 unreliable(类似 UDP),虽然快但会丢包。如果你要传文件、保存操作记录、同步光标位置,必须用 WebSocket 兜底。
- 需要广播/群发——WebRTC 是点对点,发一条消息给 100 个人就得建 100 个连接。WebSocket 配合服务端可以轻松做广播,效率高得多。
我的经验:有一次做在线教育,学生端频繁掉线,排查半天发现是 DataChannel 丢包导致关键指令没收到。后来把「举手」「下课」这类控制指令全走 WebSocket,问题立刻解决。
数据分流策略
混合架构的核心就一句话:谁擅长什么,谁就干什么。
我习惯按数据特征做分流,下面这张表是我在项目中反复打磨出来的:
| 数据类型 | 通道选择 | 原因 |
|---|---|---|
| 音视频流 | WebRTC MediaStream | 原生支持,延迟最低 |
| 实时操作(鼠标移动、画笔轨迹) | WebRTC DataChannel (unreliable) | 允许少量丢包,追求低延迟 |
| 关键指令(保存、撤销、锁定) | WebSocket | 必须可靠,不能丢 |
| 信令交换 | WebSocket | 建立连接前的必经之路 |
| 大文件/历史数据 | WebSocket + HTTP | 可靠传输,支持断点续传 |
你想想看,如果所有数据都走 WebRTC,一旦网络抖动,关键指令丢了,用户点「保存」结果没存上,那体验得多糟糕?反过来,如果所有数据都走 WebSocket,实时画笔的延迟会高到让人抓狂。
注意:不要试图在 DataChannel 上自己实现可靠传输。我见过有人给 DataChannel 加 ACK 重传机制,结果代码复杂度翻了三倍,性能还不如直接用 WebSocket。专业的事交给专业的通道。
实战案例:实时协作白板
好了,理论说完了,咱们直接上手。下面是一个简化版的白板协作系统,核心逻辑就三步:
- 用 WebSocket 交换信令,建立 WebRTC 连接
- 画笔轨迹走 DataChannel(unreliable),追求实时性
- 保存、撤销、清屏等操作走 WebSocket,保证可靠性
第一步:信令交换(WebSocket)
// 客户端:通过 WebSocket 发送 SDP Offer
const ws = new WebSocket('wss://signaling.example.com');
ws.onopen = () => {
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
ws.send(JSON.stringify({ type: 'offer', sdp: offer }));
};
// 服务端:转发给目标客户端
ws.on('message', (data) => {
const msg = JSON.parse(data);
// 根据 room 和 target 转发
broadcastToRoom(msg.room, msg);
});
第二步:建立 DataChannel
// 发起方创建 DataChannel
const dataChannel = peerConnection.createDataChannel('whiteboard', {
ordered: false, // 不保证顺序
maxRetransmits: 0 // 不重传,追求低延迟
});
// 接收方监听 DataChannel 事件
peerConnection.ondatachannel = (event) => {
const receiveChannel = event.channel;
receiveChannel.onmessage = (e) => {
// 直接绘制,不等待确认
drawStroke(JSON.parse(e.data));
};
};
第三步:数据分流
// 画笔轨迹 → DataChannel(快,允许丢)
function onMouseMove(x, y) {
const packet = { x, y, timestamp: Date.now() };
dataChannel.send(JSON.stringify(packet));
}
// 保存操作 → WebSocket(可靠,必须成功)
function onSave() {
ws.send(JSON.stringify({ type: 'save', boardId: '123' }));
}
// 撤销操作 → WebSocket(可靠)
function onUndo() {
ws.send(JSON.stringify({ type: 'undo', stepId: lastStepId }));
}
嗯,这里要注意:DataChannel 的 ordered: false 意味着后发的数据可能先到。对于白板来说,画笔轨迹稍微乱序问题不大,但如果你做的是文字输入,就得用 ordered: true 了。
避坑指南:我曾经在 DataChannel 里传 JSON 字符串,发现频繁 GC 导致卡顿。后来改用二进制格式(比如用 FlatBuffers 编码),性能提升很明显。如果你的白板需要支持 10 人以上同时画,建议走二进制。
架构总览
下面这张 SVG 图展示了整个混合架构的数据流向,我建议你仔细看看分流决策的部分:
从图上你能看到:信令走 WebSocket 建立连接,连接建立后,实时画笔走 DataChannel 直连,保存/撤销等关键操作依然走 WebSocket 中转。这就是典型的「控制与数据分离」策略。
写在最后
混合架构不是炫技,而是解决实际问题的手段。我见过太多项目一开始只用 WebSocket,后来发现实时性不够;也见过只用 WebRTC,结果信令和可靠性搞得一团糟。
我的建议是:先画清楚你的数据分类,再选通道。实时性要求高的走 WebRTC,可靠性要求高的走 WebSocket,两者互补,才是最佳实践。
一句话总结:WebRTC 负责「快」,WebSocket 负责「稳」。快稳结合,你的实时应用才能跑得又顺又可靠。
公众号:蓝海资料掘金营,微信deep3321