9、RTCDataChannel数据传输:创建DataChannel、发送文本消息、发送二进制数据(Blob/ArrayBuffer)、DataChannel的可靠与有序模式

聊完了音视频流,咱们来聊聊另一个很实用的东西——DataChannel。

说白了,DataChannel 就是在 WebRTC 的 P2P 连接上,开了一条「数据高速公路」。你可以在上面跑文本、跑文件、跑游戏指令,甚至跑控制信令。我最早接触它的时候,觉得这玩意儿简直就是浏览器里的 UDP/TCP 合体版。

嗯,今天我们就把它彻底搞明白。

9.1 创建 DataChannel:从连接中「拉」出一条数据通道

创建 DataChannel 有两种方式。一种是主动创建,另一种是在远端创建后,本地被动接收。

我个人习惯在 createOffer 之前就把 DataChannel 建好。为什么呢?因为这样 SDP 里会自动带上 DataChannel 的协商信息,省得后面再折腾。

核心原则:谁创建 DataChannel,谁就是发起方。发起方调用 createDataChannel(),接收方监听 ondatachannel 事件。
// 发起方:在创建 Offer 之前,先创建 DataChannel
const dataChannel = peerConnection.createDataChannel('myLabel', {
  ordered: true,
  maxRetransmits: 3
});

// 接收方:监听 ondatachannel 事件
peerConnection.ondatachannel = (event) => {
  const receiveChannel = event.channel;
  console.log('收到远端 DataChannel:', receiveChannel.label);
};

这里有个细节——label 只是个名字,你可以随便起。但如果你有多个 DataChannel,建议用有意义的名称,比如 chatfile-transfergame-input。我在项目中就吃过亏,全叫 data,调试时根本分不清谁是谁。

9.2 发送文本消息:最基础的数据传输

DataChannel 发送文本,简单到令人发指。直接调用 send() 方法就行。

// 发送文本
dataChannel.send('你好,世界!');

// 发送 JSON 对象
const message = {
  type: 'chat',
  content: 'Hello WebRTC',
  timestamp: Date.now()
};
dataChannel.send(JSON.stringify(message));

你可能会问:能直接发对象吗?不行。DataChannel 的 send() 只接受 stringBlobArrayBufferArrayBufferView。所以发对象前,记得先 JSON.stringify

小技巧:接收端收到文本后,可以用 JSON.parse 还原成对象。我习惯在消息里加一个 type 字段,这样接收方就能根据类型做不同处理。

9.3 发送二进制数据:Blob 与 ArrayBuffer

文本只是开胃菜。真正硬核的是二进制数据传输——比如传文件、传图片、传视频片段。

DataChannel 支持两种二进制格式:BlobArrayBuffer。怎么选?

  • Blob:适合直接传文件。比如用户选了一个图片,你拿到 File 对象(它继承自 Blob),直接 send() 出去。
  • ArrayBuffer:适合需要精细控制二进制数据的场景。比如游戏中的坐标数据、自定义协议包。
// 发送 Blob(比如用户选择的文件)
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (e) => {
  const file = e.target.files[0];
  dataChannel.send(file); // 直接发送 File 对象,它本身就是 Blob
});

// 发送 ArrayBuffer
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setInt32(0, 12345, true); // 小端序写入一个整数
view.setFloat32(4, 3.14, true); // 小端序写入一个浮点数
dataChannel.send(buffer);

接收端怎么区分是文本还是二进制?很简单,看 event.data 的类型。

dataChannel.onmessage = (event) => {
  if (typeof event.data === 'string') {
    console.log('收到文本:', event.data);
  } else if (event.data instanceof Blob) {
    console.log('收到 Blob,大小:', event.data.size);
    // 可以转成 URL 展示图片
    const url = URL.createObjectURL(event.data);
    document.getElementById('image').src = url;
  } else if (event.data instanceof ArrayBuffer) {
    console.log('收到 ArrayBuffer,字节长度:', event.data.byteLength);
    const view = new DataView(event.data);
    const intVal = view.getInt32(0, true);
    const floatVal = view.getFloat32(4, true);
    console.log('解析数据:', intVal, floatVal);
  }
};
注意:发送大文件时,DataChannel 有消息大小限制。浏览器一般限制单条消息不超过 16KB 到 64KB(取决于实现)。我曾经传一个 10MB 的文件,直接一条 send 出去,结果消息被静默丢弃了。正确的做法是分片发送,接收端再重组。

9.4 可靠与有序模式:选 TCP 还是 UDP?

DataChannel 最灵活的地方在于,你可以自己选传输模式。它底层基于 SCTP 协议,但暴露给开发者的配置很简单——两个参数:orderedmaxRetransmits / maxPacketLifeTime

模式 ordered maxRetransmits maxPacketLifeTime 行为描述
可靠有序 true 不设(或设大值) 不设 类似 TCP,保证所有数据到达且顺序正确
可靠无序 false 不设(或设大值) 不设 保证到达,但不保证顺序
不可靠有序 true 设有限值(如 3) 不设 最多重传 3 次,超时则丢弃,但保证顺序
不可靠无序 false 不设 设有限值(如 500ms) 数据包最多存活 500ms,超时丢弃,不保证顺序

你想想看,这四种模式分别适合什么场景?

  • 可靠有序:文件传输、聊天消息。丢一条消息都不行,顺序也不能乱。
  • 可靠无序:某些控制信令。消息必须到达,但顺序无所谓。
  • 不可靠有序:实时游戏中的状态同步。可以丢几帧,但顺序不能乱。
  • 不可靠无序:音视频数据(但音视频一般走 MediaStream,不走 DataChannel)。或者高频传感器数据,丢了就丢了,新的更重要。
我的经验:做实时协作编辑时,我用了可靠有序模式。因为用户的每一次按键都必须按顺序到达,否则文档就乱套了。但做屏幕共享的鼠标轨迹时,我用了不可靠无序模式——鼠标位置每秒上报 60 次,丢几帧根本看不出来,反而能降低延迟。

9.5 核心逻辑流程图

下面这张图,把 DataChannel 的创建、发送、接收、模式选择串起来了。你可以把它当作本章的知识地图。

RTCDataChannel 核心逻辑 创建 DataChannel 发起方:createDataChannel() 接收方:ondatachannel 事件 配置模式:ordered / maxRetransmits / maxPacketLifeTime 可靠有序 可靠无序 不可靠有序 不可靠无序 发送数据:send(string) / send(Blob) / send(ArrayBuffer)

9.6 避坑指南

最后,分享几个我踩过的坑。

  • DataChannel 状态检查:发送数据前,一定要检查 dataChannel.readyState 是否为 'open'。我曾经在 connecting 状态就发数据,结果消息丢了,排查了半天。
  • 消息大小限制:前面提过,单条消息不要太大。浏览器实现不同,安全做法是每条消息不超过 16KB。大文件一定要分片。
  • 关闭连接:不再使用 DataChannel 时,记得调用 dataChannel.close()。否则底层 SCTP 连接会一直占用资源。
  • 二进制类型设置:可以通过 dataChannel.binaryType = 'arraybuffer''blob' 来指定接收二进制数据的格式。默认是 'blob'。如果你需要频繁操作二进制数据,建议设为 'arraybuffer',性能更好。
一句话总结:DataChannel 就是 WebRTC 里的瑞士军刀。文本、文件、游戏数据,全都能跑。选对模式,事半功倍。

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