第7章:数据通道:RTCDataChannel API、可靠与不可靠模式、文件传输实战

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

很多人觉得WebRTC就是用来做视频通话的。其实不然。我个人认为,数据通道才是WebRTC里被低估的宝藏。它让你在浏览器之间,直接传任意数据——文本、二进制、文件,甚至游戏状态。而且,不需要经过服务器中转。

说白了,这就是浏览器原生的P2P数据传输能力。

7.1 RTCDataChannel 是什么?

RTCDataChannel,基于SCTP协议运行在DTLS之上。它和WebSocket有点像,但有一个本质区别:WebSocket走的是服务器中转,DataChannel走的是点对点。

你想想看,两个浏览器直接建一条数据管道,中间没有服务器转发。延迟低、隐私好、带宽省。我在做远程桌面项目时,就用它传鼠标坐标和键盘事件,延迟基本感觉不到。

核心特点:

  • 基于SCTP(流控制传输协议)
  • 运行在加密的DTLS通道之上
  • 支持可靠和不可靠两种模式
  • 支持字符串和二进制数据
  • 最大消息长度受浏览器限制(通常16KB-256KB)

7.2 可靠模式 vs 不可靠模式

这是DataChannel最灵活的地方。你可以按需选择。

特性 可靠模式 不可靠模式
数据保证 100%送达,顺序保证 可能丢包,可能乱序
适用场景 文件传输、聊天消息 实时游戏、直播弹幕
底层机制 SCTP可靠传输 SCTP部分可靠
延迟表现 可能因重传而变高 低延迟,丢包不重传

为什么会这样?因为可靠模式下,SCTP会重传丢失的数据包。这在网络差的时候,延迟会飙升。而不可靠模式,说白了就是「丢了就丢了」,适合那些对实时性要求高、对完整性要求低的场景。

我记得有一次做在线协作白板,笔迹数据用了可靠模式。结果网络一波动,画笔画到一半卡住了,等恢复后突然刷出一堆线条。后来改成不可靠模式,配合本地补偿算法,体验好多了。

7.3 创建数据通道

创建DataChannel很简单。你只需要在建立PeerConnection之后,调用createDataChannel方法。

// 创建数据通道
const dataChannel = peerConnection.createDataChannel('myChannel', {
  ordered: true,        // 是否保证顺序
  maxRetransmits: 3,    // 最大重传次数(不可靠模式)
  // 或者使用 maxPacketLifeTime: 3000  // 最大生存时间(毫秒)
});

// 监听通道打开
dataChannel.onopen = () => {
  console.log('数据通道已打开');
  dataChannel.send('Hello, WebRTC!');
};

// 监听消息
dataChannel.onmessage = (event) => {
  console.log('收到消息:', event.data);
};

// 监听关闭
dataChannel.onclose = () => {
  console.log('数据通道已关闭');
};

小技巧:如果你不设置maxRetransmitsmaxPacketLifeTime,默认就是可靠模式。我个人习惯在创建时明确指定,避免混淆。

7.4 发送和接收数据

DataChannel支持多种数据类型:字符串、Blob、ArrayBuffer、ArrayBufferView。

// 发送字符串
dataChannel.send('这是一条文本消息');

// 发送二进制数据
const buffer = new ArrayBuffer(1024);
dataChannel.send(buffer);

// 发送Blob(比如文件分片)
const blob = new Blob(['文件内容'], { type: 'text/plain' });
dataChannel.send(blob);

接收端怎么判断数据类型?看event.data的类型就行。

dataChannel.onmessage = (event) => {
  if (typeof event.data === 'string') {
    console.log('文本消息:', event.data);
  } else if (event.data instanceof ArrayBuffer) {
    console.log('二进制数据:', event.data.byteLength + '字节');
  } else if (event.data instanceof Blob) {
    console.log('Blob数据:', event.data.size + '字节');
  }
};

注意:浏览器对单条消息大小有限制。Chrome大约是256KB,Firefox是16KB。传大文件时,一定要分片发送。我曾经踩过这个坑,传一个几MB的文件直接报错,后来老老实实分了片。

7.5 文件传输实战

好,咱们来点实战。文件传输是DataChannel最典型的应用场景。核心思路就是:把文件切成小块,一块一块发过去,接收端再拼起来。

7.5.1 发送端代码

function sendFile(file, dataChannel) {
  const chunkSize = 16 * 1024; // 16KB 分片
  let offset = 0;

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

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

    // 计算进度
    const progress = (offset / file.size) * 100;
    console.log(`发送进度: ${progress.toFixed(2)}%`);

    // 继续读取下一片
    if (offset < file.size) {
      readSlice();
    } else {
      console.log('文件发送完成');
    }
  };

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

  readSlice();
}

7.5.2 接收端代码

let receivedChunks = [];
let fileMetadata = null;
let receivedSize = 0;

dataChannel.onmessage = (event) => {
  // 第一条消息是元信息
  if (!fileMetadata) {
    fileMetadata = JSON.parse(event.data);
    console.log(`开始接收文件: ${fileMetadata.name}`);
    return;
  }

  // 后续消息是文件分片
  receivedChunks.push(event.data);
  receivedSize += event.data.byteLength || event.data.size;

  const progress = (receivedSize / fileMetadata.size) * 100;
  console.log(`接收进度: ${progress.toFixed(2)}%`);

  // 接收完成,拼装文件
  if (receivedSize >= fileMetadata.size) {
    const blob = new Blob(receivedChunks, { type: fileMetadata.type });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = fileMetadata.name;
    a.click();
    URL.revokeObjectURL(url);
    console.log('文件接收完成');
  }
};

关键点总结:

  • 先发元信息(文件名、大小、类型)
  • 分片大小建议16KB-64KB
  • 接收端用Blob拼装
  • 记得释放URL对象

7.6 数据通道的知识体系

下面这张图,帮你理清DataChannel的核心脉络。

RTCDataChannel 创建方式 createDataChannel(name, options) ondatachannel 事件监听 传输模式 可靠模式 (ordered: true) 不可靠模式 (maxRetransmits) 数据类型 字符串 / 文本 ArrayBuffer / Blob 文件传输 实时游戏 聊天/信令 基于SCTP + DTLS,浏览器原生P2P数据通道

7.7 避坑指南

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

  • 消息大小限制:不同浏览器限制不同。Chrome单条最大256KB,Firefox只有16KB。传大文件必须分片。
  • 通道状态检查:发送数据前,一定要检查dataChannel.readyState是否为'open'。否则会抛异常。
  • 并发发送:DataChannel内部有流控,但如果你连续快速发送大量小消息,可能会触发背压。建议用队列控制发送节奏。
  • 关闭时机:不要在onmessage里直接关闭通道。我曾经在接收完最后一片数据后立刻关闭,结果导致部分数据丢失。加个setTimeout延迟关闭会安全很多。

嗯,数据通道的内容就这些。它不复杂,但很实用。你想想看,浏览器之间直接传文件,不需要服务器,不需要插件,原生支持——这在几年前简直不敢想。现在,一行代码就能搞定。


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