18、WebRTC与WebSocket混合架构:何时需要混合使用、数据分流策略、实战案例:实时协作白板

说实话,做了这么多年实时通信,我见过太多人一上来就问:「WebRTC 能不能完全替代 WebSocket?」

我的回答一直是:不能,也不该

WebRTC 和 WebSocket 不是对手,而是搭档。就像你盖房子,WebSocket 是地基和管道,WebRTC 是高速电梯。各有各的活儿,硬要混着用反而坏事。

这一章,我就带你看看什么时候该混合用,怎么分流数据,最后用白板实战把整个流程串起来。

核心观点:WebRTC 负责低延迟、高吞吐的媒体流和实时数据通道;WebSocket 负责信令、状态同步、非实时控制指令。两者互补,缺一不可。

什么时候需要混合使用?

我总结了三类场景,只要命中其中一条,你就该考虑混合架构了:

  1. 需要信令通道——WebRTC 建立连接前必须交换 SDP 和 ICE 候选,WebSocket 是最优雅的信令载体。我早期用 HTTP 轮询做信令,延迟高得离谱,后来全换成 WebSocket,体验直接起飞。
  2. 需要可靠传输——WebRTC 的 DataChannel 默认是 unreliable(类似 UDP),虽然快但会丢包。如果你要传文件、保存操作记录、同步光标位置,必须用 WebSocket 兜底。
  3. 需要广播/群发——WebRTC 是点对点,发一条消息给 100 个人就得建 100 个连接。WebSocket 配合服务端可以轻松做广播,效率高得多。

我的经验:有一次做在线教育,学生端频繁掉线,排查半天发现是 DataChannel 丢包导致关键指令没收到。后来把「举手」「下课」这类控制指令全走 WebSocket,问题立刻解决。

数据分流策略

混合架构的核心就一句话:谁擅长什么,谁就干什么

我习惯按数据特征做分流,下面这张表是我在项目中反复打磨出来的:

数据类型 通道选择 原因
音视频流 WebRTC MediaStream 原生支持,延迟最低
实时操作(鼠标移动、画笔轨迹) WebRTC DataChannel (unreliable) 允许少量丢包,追求低延迟
关键指令(保存、撤销、锁定) WebSocket 必须可靠,不能丢
信令交换 WebSocket 建立连接前的必经之路
大文件/历史数据 WebSocket + HTTP 可靠传输,支持断点续传

你想想看,如果所有数据都走 WebRTC,一旦网络抖动,关键指令丢了,用户点「保存」结果没存上,那体验得多糟糕?反过来,如果所有数据都走 WebSocket,实时画笔的延迟会高到让人抓狂。

注意:不要试图在 DataChannel 上自己实现可靠传输。我见过有人给 DataChannel 加 ACK 重传机制,结果代码复杂度翻了三倍,性能还不如直接用 WebSocket。专业的事交给专业的通道。

实战案例:实时协作白板

好了,理论说完了,咱们直接上手。下面是一个简化版的白板协作系统,核心逻辑就三步:

  1. 用 WebSocket 交换信令,建立 WebRTC 连接
  2. 画笔轨迹走 DataChannel(unreliable),追求实时性
  3. 保存、撤销、清屏等操作走 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 图展示了整个混合架构的数据流向,我建议你仔细看看分流决策的部分:

WebRTC + WebSocket 混合架构数据流 客户端 A DataChannel (unreliable) WebSocket (reliable) MediaStream 客户端 B DataChannel (unreliable) WebSocket (reliable) MediaStream 信令服务器 (WebSocket) 交换 SDP / ICE Candidate 分流决策器 实时操作 → DataChannel 关键指令 → WebSocket 白板应用层 绘制 / 保存 / 撤销 信令 信令 数据 数据 P2P DataChannel / MediaStream 图例: P2P 直连 信令 数据流 分流决策

从图上你能看到:信令走 WebSocket 建立连接,连接建立后,实时画笔走 DataChannel 直连,保存/撤销等关键操作依然走 WebSocket 中转。这就是典型的「控制与数据分离」策略。

写在最后

混合架构不是炫技,而是解决实际问题的手段。我见过太多项目一开始只用 WebSocket,后来发现实时性不够;也见过只用 WebRTC,结果信令和可靠性搞得一团糟。

我的建议是:先画清楚你的数据分类,再选通道。实时性要求高的走 WebRTC,可靠性要求高的走 WebSocket,两者互补,才是最佳实践。

一句话总结:WebRTC 负责「快」,WebSocket 负责「稳」。快稳结合,你的实时应用才能跑得又顺又可靠。


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