9、数据通道:RTCDataChannel创建、可靠与不可靠模式、文件传输实战

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

很多人觉得WebRTC就是用来传视频和音频的。其实不然。WebRTC的核心能力是建立点对点连接,而数据通道(RTCDataChannel)就是这条连接上的一条“高速公路”。你可以用它传任何数据——文本、二进制、文件,甚至游戏指令。

我个人习惯把数据通道比作“WebSocket的P2P版本”。区别在哪?WebSocket走服务器中转,数据通道直接点对点。延迟更低,隐私更好。我在项目中遇到过不少场景,比如远程桌面、文件共享、实时白板,都是靠数据通道搞定的。

核心要点:RTCDataChannel基于SCTP协议,运行在DTLS加密通道之上。它天生安全,延迟可控。

9.1 数据通道的创建

创建数据通道很简单。你只需要一个RTCPeerConnection实例,然后调用它的createDataChannel方法。

const peerConnection = new RTCPeerConnection(config);
const dataChannel = peerConnection.createDataChannel('myLabel', {
  ordered: true,
  maxRetransmits: 3
});

这里有个细节:createDataChannel必须在发起连接之前调用。为什么?因为SCTP的配置是在连接建立时协商的。如果你在连接建立后才创建通道,虽然也能用,但某些参数可能无法生效。

嗯,这里要注意:createDataChannel是本地创建,对方需要通过ondatachannel事件来接收。

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

说白了,创建数据通道就像两个人约定好一个暗号。你这边喊一声“我要建一个叫myLabel的通道”,对方那边就会收到通知。两边都准备好了,数据就能跑了。

9.2 可靠模式 vs 不可靠模式

数据通道有两种模式:可靠和不可靠。这个选择直接影响你的应用行为。

特性 可靠模式 不可靠模式
数据保证 100%送达 可能丢失
顺序保证 有序(默认)或无序 无序
重传机制 自动重传 不重传或有限重传
适用场景 文件传输、聊天消息 实时游戏、视频帧数据

可靠模式,说白了就是TCP那套——保证数据不丢、不乱序。你传一个文件,对方必须完整收到,顺序也不能错。否则文件就坏了。

不可靠模式,类似UDP。数据可能丢,顺序可能乱。但换来的是低延迟。我在项目中做过一个实时游戏,玩家位置更新用不可靠模式。丢几帧没关系,但延迟必须低。你想想看,如果玩家移动要等重传,那游戏就没法玩了。

我的建议:如果你不确定用哪种,先问自己一个问题——“数据丢了会怎样?”如果会出大问题,用可靠模式。如果只是体验变差,用不可靠模式。

设置模式其实是通过参数控制的:

// 可靠模式(默认)
const reliable = pc.createDataChannel('reliable');

// 不可靠模式:最多重传3次
const unreliable = pc.createDataChannel('unreliable', {
  ordered: false,
  maxRetransmits: 3
});

// 不可靠模式:最多存活30ms
const timed = pc.createDataChannel('timed', {
  ordered: false,
  maxPacketLifeTime: 30
});

注意:maxRetransmitsmaxPacketLifeTime不能同时设置。这是SCTP协议的限制。我曾经踩过这个坑,两个都设了,结果通道创建失败。调试了半天才发现。

9.3 文件传输实战

文件传输是数据通道的经典应用。咱们来写一个完整的例子。

核心思路:把文件切成小块,通过数据通道发送。接收方收到后重组。

// 发送端
function sendFile(file, dataChannel) {
  const chunkSize = 16384; // 16KB
  let offset = 0;

  // 先发送文件元信息
  dataChannel.send(JSON.stringify({
    type: 'meta',
    name: file.name,
    size: file.size
  }));

  // 然后发送文件数据
  const reader = new FileReader();
  reader.onload = (e) => {
    dataChannel.send(e.target.result);
    offset += e.target.result.byteLength;

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

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

  readNextChunk();
}

接收端这边:

// 接收端
let receivedBuffers = [];
let fileMeta = null;

dataChannel.onmessage = (event) => {
  const data = event.data;

  if (typeof data === 'string') {
    // 解析元信息
    fileMeta = JSON.parse(data);
    console.log('收到文件:', fileMeta.name, fileMeta.size);
    return;
  }

  // 收到二进制数据
  receivedBuffers.push(data);

  // 检查是否接收完成
  const totalReceived = receivedBuffers.reduce((acc, buf) => acc + buf.byteLength, 0);
  if (totalReceived >= fileMeta.size) {
    // 重组文件
    const blob = new Blob(receivedBuffers);
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = fileMeta.name;
    a.click();
    URL.revokeObjectURL(url);
  }
};

这里有个关键点:分片大小。我习惯用16KB。为什么?因为SCTP的MTU一般是1200字节左右,但数据通道内部会做分片和重组。16KB是一个比较平衡的值——不会因为太小导致过多消息,也不会因为太大导致内存压力。

避坑指南:我曾经在传输大文件时遇到内存溢出。原因是把所有分片都保存在内存里,等全部收完才重组。后来我改成边收边写,用Blob的slice方法逐步构建。或者用IndexedDB做临时存储。大文件一定要考虑内存。

9.4 数据通道的注意事项

最后聊几个实战中容易踩的坑。

  • 消息大小限制:单个消息不要超过64KB。虽然SCTP支持更大的消息,但浏览器实现有限制。我建议控制在16KB以内。
  • 通道数量:理论上可以创建多个通道,但实际不要超过10个。每个通道都有开销。
  • 连接状态:发送数据前检查readyState。如果通道没打开,数据会丢失。
  • 二进制数据:使用ArrayBuffer而不是Blob。Blob在传输时会被转为ArrayBuffer,多一次拷贝。

数据通道是个好东西。它让WebRTC不仅仅是音视频的专利。文件传输、游戏同步、实时协作,都能靠它搞定。你想想看,一个浏览器就能直接给另一个浏览器传文件,不需要服务器中转,多酷。

总结:数据通道的核心是SCTP。可靠模式保证数据完整,不可靠模式追求低延迟。文件传输要分片、要控制内存。记住这些,你就能用好数据通道。

RTCDataChannel 核心逻辑 createDataChannel 选择模式:可靠 / 不可靠 可靠模式 有序 + 自动重传 适用:文件传输、聊天 不可靠模式 无序 + 有限重传/超时 适用:游戏、实时数据 文件传输 / 游戏同步 / 实时协作
公众号:蓝海资料掘金营,微信deep3321