6、数据通道进阶:可靠与不可靠模式、消息大小限制与分片、发送二进制数据(Blob/ArrayBuffer)

数据通道(DataChannel)是 WebRTC 里我最喜欢的部分。为什么?因为它让你在音视频之外,还能自由地传任何数据。文本、文件、游戏状态、实时指令……说白了,它就是一条浏览器之间的“TCP/UDP 二合一”管道。

但很多同学用 DataChannel 时,只停留在 send('hello') 的层面。一旦遇到大文件、二进制数据、或者需要控制传输可靠性时,就懵了。今天我们就来把这些硬骨头啃掉。

本章核心三问:
  1. 什么时候用可靠模式?什么时候用不可靠模式?
  2. 消息最大能发多大?超了怎么办?
  3. 二进制数据(Blob/ArrayBuffer)怎么发?

6.1 可靠与不可靠模式:选对模式,事半功倍

创建 DataChannel 时,有两个关键参数:orderedmaxRetransmits(或 maxPacketLifeTime)。它们决定了通道的行为。

模式 ordered maxRetransmits / maxPacketLifeTime 行为
可靠、有序 true(默认) 不设置 类似 TCP:保证到达,保证顺序
不可靠、无序 false 设置其中一个 类似 UDP:可能丢包,不保证顺序
不可靠、有序 true 设置其中一个 保证顺序,但允许丢包(跳过丢失的)

我个人习惯:传文件、关键指令用可靠模式;传实时位置、游戏状态用不可靠模式。你想想看,如果游戏里每个玩家的坐标都要重传,那延迟就爆炸了。

我的经验: 我在做一个远程协作白板项目时,笔迹数据用了可靠模式。结果网络差的时候,画一笔要等半秒。后来改成不可靠模式,丢几帧笔迹用户根本看不出来,流畅度提升明显。

代码示例:创建两种通道

// 可靠通道:传文件
const fileChannel = peerConnection.createDataChannel('file-transfer', {
  ordered: true
});

// 不可靠通道:传实时位置
const positionChannel = peerConnection.createDataChannel('position', {
  ordered: false,
  maxRetransmits: 0  // 不重传
});
注意: maxRetransmitsmaxPacketLifeTime 不能同时设置。前者限制重传次数,后者限制消息存活时间(毫秒)。选一个就行。

6.2 消息大小限制与分片:别想一次发太大

很多人以为 DataChannel 能随便发大消息。错!SCTP 协议底层有消息大小限制。不同浏览器不一样,但通常上限是 16KB 到 64KB 之间。

为什么会这样?因为 SCTP 把消息切成小块(chunk)传输,每个 chunk 最大 64KB。如果消息超过这个值,浏览器会直接报错或静默失败。

我记得有一次,用户上传一张 5MB 的图片,我用 channel.send(blob) 直接发,结果 Chrome 控制台报了个 Message too large。嗯,从那以后我再也不偷懒了。

分片策略:自己动手切

解决方案很简单:把大消息切成小块,每块单独发送,接收端再拼回去

const CHUNK_SIZE = 16 * 1024; // 16KB 一块

function sendLargeMessage(channel, data) {
  // data 是 ArrayBuffer
  const totalChunks = Math.ceil(data.byteLength / CHUNK_SIZE);
  
  // 先发送元信息
  channel.send(JSON.stringify({
    type: 'meta',
    totalChunks: totalChunks,
    totalSize: data.byteLength
  }));

  // 逐块发送
  for (let i = 0; i < totalChunks; i++) {
    const start = i * CHUNK_SIZE;
    const end = Math.min(start + CHUNK_SIZE, data.byteLength);
    const chunk = data.slice(start, end);
    channel.send(chunk);
  }
}
避坑指南: 我曾经在分片时忘了先发元信息,接收端不知道总共有多少片,结果拼到一半就卡住了。所以一定要先告诉对方“我要发 N 片,总大小是 M”。

接收端拼装

let receivedChunks = [];
let expectedChunks = 0;
let totalSize = 0;

channel.onmessage = (event) => {
  if (typeof event.data === 'string') {
    const meta = JSON.parse(event.data);
    if (meta.type === 'meta') {
      expectedChunks = meta.totalChunks;
      totalSize = meta.totalSize;
      receivedChunks = [];
    }
  } else {
    // 二进制数据
    receivedChunks.push(event.data);
    if (receivedChunks.length === expectedChunks) {
      // 拼装完成
      const blob = new Blob(receivedChunks, { type: 'application/octet-stream' });
      // 处理 blob...
    }
  }
};

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

DataChannel 原生支持发送 stringBlobArrayBufferArrayBufferView。但要注意:发送 Blob 时,浏览器会自动把它转成 ArrayBuffer,所以底层其实都是二进制。

我个人更推荐直接用 ArrayBuffer,因为控制更精细。比如你要发一个自定义协议的数据包,用 ArrayBuffer 可以精确控制字节偏移。

发送 ArrayBuffer

// 创建一个 4 字节的 ArrayBuffer
const buf = new ArrayBuffer(4);
const view = new Uint8Array(buf);
view[0] = 0x01; // 消息类型
view[1] = 0x00; // 保留
view[2] = 0x10; // 数据长度高位
view[3] = 0x20; // 数据长度低位

channel.send(buf);

发送 Blob

// 从文件输入获取 Blob
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (e) => {
  const file = e.target.files[0];
  // 直接发送 Blob(但注意大小限制!)
  // channel.send(file);
  
  // 安全做法:先转成 ArrayBuffer,再分片
  file.arrayBuffer().then(buffer => {
    sendLargeMessage(channel, buffer);
  });
});
重要: 发送 Blob 时,如果 Blob 太大(比如超过 16KB),浏览器可能直接拒绝。所以即使你用 Blob,也建议先转成 ArrayBuffer 再分片。

6.4 知识体系图:数据通道进阶全景

DataChannel 进阶 可靠模式 ordered: true 不设 maxRetransmits 类似 TCP:保证到达与顺序 不可靠模式 ordered: false 设 maxRetransmits 或 maxPacketLifeTime 类似 UDP:允许丢包与乱序 消息大小限制 SCTP 限制:16KB ~ 64KB 解决方案:分片发送 二进制数据 Blob / ArrayBuffer 推荐用 ArrayBuffer 精细控制 选对模式 + 分片 + 二进制 = 实战利器

6.5 总结

数据通道进阶,说白了就三件事:

  • 选模式:可靠还是不可靠?看场景。别无脑用可靠,也别为了性能全用不可靠。
  • 分片:消息有大小限制,超过 16KB 就自己切。先发元信息,再发数据块,接收端拼回去。
  • 二进制:ArrayBuffer 是王道,Blob 是便利。大文件一定要分片,别偷懒。

嗯,这些坑我都踩过。希望你能少走弯路。


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