6、数据通道:RTCDataChannel的创建与配置、发送文本与二进制数据、文件传输实战

聊完了音视频,咱们来聊聊数据通道。

说实话,我刚开始接触 WebRTC 时,注意力全在视频通话上。后来才发现,RTCDataChannel 才是真正的宝藏。它让你在浏览器之间直接传数据,不需要经过服务器中转。延迟低、速度快,而且加密传输。

说白了,它就是一条浏览器之间的“专线”。

6.1 数据通道是什么?

RTCDataChannel 基于 SCTP 协议运行在 DTLS 加密之上。你可以把它想象成 WebSocket 的升级版——但不需要服务器。

它有两个核心模式:

  • 可靠有序模式:类似 TCP,保证数据到达且顺序正确。适合文件传输。
  • 不可靠无序模式:类似 UDP,数据可能丢包或乱序。适合游戏、实时消息。

关键点:数据通道和媒体流走的是同一个 ICE 连接。所以一旦 P2P 连接建立,你就能同时传音视频和数据。

6.2 创建与配置

创建数据通道有两种方式。我习惯在发起方直接创建,在接收方监听事件。

发起方创建

const dataChannel = peerConnection.createDataChannel('myChannel', {
  ordered: true,          // 是否保证顺序
  maxRetransmits: 3,      // 最大重传次数(不可靠模式用)
  maxPacketLifeTime: 1000 // 最大存活时间(毫秒)
});

这里要注意:maxRetransmitsmaxPacketLifeTime 不能同时设置。选一个就好。

接收方监听

peerConnection.ondatachannel = (event) => {
  const dataChannel = event.channel;
  console.log('收到数据通道:', dataChannel.label);
};

嗯,这里有个坑。我曾经在项目中忘记在发起方先调用 createDataChannel,结果接收方死活收不到 ondatachannel 事件。记住:发起方必须先创建,通道信息会通过 SDP 协商传递给对方。

6.3 发送文本与二进制数据

数据通道支持发送字符串、ArrayBuffer、Blob。接口非常简单:

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

// 发送二进制
const buffer = new ArrayBuffer(8);
const view = new Uint8Array(buffer);
view[0] = 0xFF;
dataChannel.send(buffer);

// 发送 Blob
const blob = new Blob(['Hello'], { type: 'text/plain' });
dataChannel.send(blob);

接收数据时,通过 onmessage 事件处理:

dataChannel.onmessage = (event) => {
  if (event.data instanceof ArrayBuffer) {
    console.log('收到二进制数据,长度:', event.data.byteLength);
  } else if (event.data instanceof Blob) {
    console.log('收到 Blob,类型:', event.data.type);
  } else {
    console.log('收到文本:', event.data);
  }
};

我的经验:发送大文件时,建议把数据切成小块(比如 16KB 一个 chunk)。我曾经一次性发送 10MB 的 ArrayBuffer,结果浏览器直接报错。后来改成切片发送,稳得很。

6.4 文件传输实战

咱们来写一个完整的文件传输示例。这个我做过好几次,踩过不少坑。

发送端代码

const CHUNK_SIZE = 16384; // 16KB

function sendFile(file, dataChannel) {
  const reader = new FileReader();
  let offset = 0;

  reader.onload = (e) => {
    dataChannel.send(e.target.result);
    offset += e.target.result.byteLength;

    if (offset < file.size) {
      readSlice();
    } else {
      console.log('文件发送完成');
    }
  };

  function readSlice() {
    const slice = file.slice(offset, offset + CHUNK_SIZE);
    reader.readAsArrayBuffer(slice);
  }

  readSlice();
}

接收端代码

let receivedBuffers = [];

dataChannel.onmessage = (event) => {
  if (event.data instanceof ArrayBuffer) {
    receivedBuffers.push(event.data);
    // 可以在这里更新进度条
  }
};

// 当通道关闭时,合并所有数据
dataChannel.onclose = () => {
  const totalLength = receivedBuffers.reduce((acc, buf) => acc + buf.byteLength, 0);
  const combined = new Uint8Array(totalLength);
  let offset = 0;

  for (const buf of receivedBuffers) {
    combined.set(new Uint8Array(buf), offset);
    offset += buf.byteLength;
  }

  const blob = new Blob([combined]);
  // 保存或展示文件
};

注意:接收端不能依赖 onclose 来判断文件传输完成。因为数据通道可能因为网络问题意外关闭。我建议在发送端发送一个特殊的“结束标记”,比如发送一个包含 { type: 'EOF', fileName: '...' } 的 JSON 消息。

6.5 知识体系图

下面这张图总结了数据通道的核心知识点:

RTCDataChannel 创建方式 配置参数 createDataChannel ondatachannel ordered maxRetransmits 支持的数据类型 传输模式 字符串 ArrayBuffer 可靠有序 不可靠无序

6.6 避坑指南

最后分享几个我踩过的坑:

  • 通道状态检查:发送数据前一定要检查 dataChannel.readyState === 'open'。我曾经在通道还没打开时就 send,结果数据丢了。
  • 大文件切片:单次发送不要超过 64KB。浏览器底层有缓冲区限制,超了会报错。
  • 连接断开处理:监听 oncloseonerror 事件。P2P 连接随时可能断开,要做好重连或提示。
  • SDP 协商顺序:创建数据通道必须在 createOffer 之前。否则通道信息不会包含在 SDP 中。

我的习惯:在项目里我会封装一个 DataChannelManager 类,统一管理创建、发送、接收、重连逻辑。这样代码干净,也方便复用。

数据通道的潜力很大。除了文件传输,你还可以用它做实时白板、游戏同步、聊天消息。说白了,只要你能想到的实时数据传输场景,它都能胜任。

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