10、数据通道:RTCDataChannel介绍、数据通道创建、文本数据传输、二进制数据传输

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

说实话,很多人一提到 WebRTC,第一反应就是视频通话。但在我实际的项目经验里,数据通道(RTCDataChannel) 的出场率一点都不比媒体流低。它就像一条隐藏在 P2P 连接里的“高速公路”,专门用来跑那些不需要实时编解码的数据。

你想想看,文件传输、游戏中的状态同步、聊天消息、甚至是一些控制信令,都可以走这条路。而且它最大的好处是——不经过服务器中转,延迟极低。

核心概念:RTCDataChannel 基于 SCTP 协议(流控制传输协议)构建,运行在 DTLS 加密通道之上。这意味着它天生就是加密的,而且支持可靠或不可靠两种传输模式。

10.1 数据通道的创建

创建数据通道其实很简单。你只需要一个已经建立好的 RTCPeerConnection 实例。

我个人习惯在 createOffer 之前就把通道建好,这样 offer 里会自动带上 data channel 的协商信息。当然,你也可以在连接建立后再创建,但那样需要额外的协商步骤。

// 创建数据通道
const dataChannel = peerConnection.createDataChannel('myChannel', {
  ordered: true,        // 保证消息顺序
  maxRetransmits: 3     // 最大重传次数(可靠模式)
});

// 监听通道状态
dataChannel.onopen = () => {
  console.log('数据通道已打开');
};

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

dataChannel.onerror = (error) => {
  console.error('数据通道错误:', error);
};

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

这里有个细节要注意:maxRetransmitsmaxPacketLifeTime 是互斥的。你只能选一个,或者都不选(那就是完全可靠模式)。

我曾经踩过的坑:在创建通道时同时设置了 maxRetransmits 和 maxPacketLifeTime,结果浏览器直接抛异常。后来查了规范才知道,这两个参数是二选一的。

10.2 文本数据传输

文本数据是最简单的。你直接调用 send() 方法就行。

// 发送文本消息
dataChannel.send('你好,这是文本消息');

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

接收端收到的是 MessageEvent,里面的 data 属性就是字符串。如果你发的是 JSON,记得在接收端 JSON.parse() 一下。

嗯,这里要提醒一句:数据通道的单个消息大小有限制。不同浏览器不一样,Chrome 大概是 16KB 左右。超过这个大小,消息会被自动分片,但接收端会重新组装好再触发 onmessage。所以从 API 层面看,你不需要关心分片逻辑。

10.3 二进制数据传输

二进制数据才是数据通道真正发光的地方。文件、图片、视频片段,都可以通过二进制通道传输。

WebRTC 支持两种二进制类型:ArrayBufferBlob。我个人更推荐 ArrayBuffer,因为它在接收端处理起来更灵活。

// 发送 ArrayBuffer
const buffer = new ArrayBuffer(8);
const view = new Uint8Array(buffer);
view[0] = 0x48; // 'H'
view[1] = 0x65; // 'e'
// ... 填充数据
dataChannel.send(buffer);

// 发送 Blob(比如文件片段)
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async (e) => {
  const file = e.target.files[0];
  const chunkSize = 16 * 1024; // 16KB 分片
  let offset = 0;

  while (offset < file.size) {
    const chunk = file.slice(offset, offset + chunkSize);
    dataChannel.send(chunk);
    offset += chunkSize;
  }
});

接收端怎么判断是文本还是二进制?很简单,检查 event.data 的类型:

dataChannel.onmessage = (event) => {
  if (event.data instanceof ArrayBuffer) {
    // 处理二进制数据
    const view = new Uint8Array(event.data);
    console.log('收到二进制数据,长度:', view.length);
  } else if (event.data instanceof Blob) {
    // 处理 Blob 数据
    const reader = new FileReader();
    reader.onload = () => {
      console.log('Blob 内容:', reader.result);
    };
    reader.readAsText(event.data);
  } else {
    // 处理文本数据
    console.log('收到文本:', event.data);
  }
};

小技巧:如果你要传大文件,建议在应用层自己做分片和确认机制。虽然 SCTP 底层会分片,但应用层的分片可以让你实现断点续传和进度显示。

10.4 数据通道的配置选项

创建数据通道时,有几个关键参数值得你关注:

参数 类型 说明
ordered boolean 是否保证消息顺序。true 表示按发送顺序接收,false 表示可以乱序
maxPacketLifeTime number 消息在通道中的最大存活时间(毫秒),超时未送达则丢弃
maxRetransmits number 最大重传次数,超过次数则丢弃消息
protocol string 子协议标识,可以用来区分不同的数据通道用途

说白了,这些参数就是让你在可靠性实时性之间做权衡。比如游戏中的位置同步,你可以用 ordered: false 加上 maxPacketLifeTime: 100,这样即使丢包了也无所谓,反正下一帧马上就来。

10.5 数据通道的生命周期

数据通道的状态变化其实很简单:

  1. connecting:通道正在建立
  2. open:通道已打开,可以收发数据
  3. closing:通道正在关闭
  4. closed:通道已关闭

RTCPeerConnection 断开时,所有关联的数据通道也会自动关闭。所以你不必手动一个个去关,但养成好习惯,在页面卸载时主动关闭通道总是没错的。

实战建议:在数据通道的 onclose 回调里,记得清理相关的 UI 状态和缓存数据。我曾经在一个项目中忘了做这件事,结果用户重新连接后,界面上还显示着旧的文件传输进度,搞得用户一头雾水。

10.6 知识体系图

下面这张图帮你理清数据通道的核心逻辑:

RTCDataChannel 核心知识体系 RTCDataChannel 创建方式 createDataChannel() 数据传输类型 文本 / ArrayBuffer / Blob 配置参数 ordered / maxRetransmits 生命周期 connecting → open → closing → closed 应用场景 文件传输 / 聊天 / 游戏同步 注意事项 消息大小限制 / 分片策略

这张图把数据通道的核心要素都串起来了。从创建到配置,从数据类型到应用场景,每一步都有对应的知识点。你在实际开发中,可以把它当作一个检查清单,看看自己有没有遗漏什么。


好了,数据通道的内容就讲到这里。文本传输、二进制传输、配置选项,这些都是在实战中高频使用的技能。下一节我们会聊聊如何用数据通道实现一个完整的文件传输功能,到时候我会分享一些我在项目中用到的实用技巧。

最后一个小建议:如果你刚开始接触数据通道,建议先从文本传输入手,跑通了再尝试二进制。这样排查问题会容易很多。

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