21、DataChannel:RTCDataChannel介绍、发送文本消息、发送文件数据
聊到 WebRTC,大家第一反应往往是音视频。但有个功能容易被忽略——DataChannel。说白了,它就是在两个浏览器之间,直接开一条数据通道。不需要经过服务器中转,数据点对点传输。
我个人习惯把 DataChannel 比作「WebSocket 的升级版」。为什么?因为 WebSocket 必须经过服务器,而 DataChannel 是端到端的。延迟更低,隐私性更强。
RTCDataChannel 是什么?
RTCDataChannel 是 WebRTC 提供的一个 API。它允许你在两个 peer 之间,传输任意数据。文本、文件、二进制数据,统统可以。
它的底层基于 SCTP 协议。SCTP 是个好东西,它结合了 TCP 的可靠性和 UDP 的低延迟。你可以自己选模式:
- 可靠模式:数据包必须到达,顺序不能乱。适合文件传输。
- 不可靠模式:允许丢包,适合实时游戏、高频数据流。
我在项目中遇到过一个问题:有个同事把 DataChannel 当 WebSocket 用,结果发现丢包率很高。后来一看,他设的是不可靠模式,但传输的是配置文件。嗯,这里要注意——选对模式很重要。
核心特点:
- 点对点传输,不经过服务器
- 支持可靠/不可靠两种模式
- 传输文本、二进制数据
- 延迟极低,适合实时场景
DataChannel 的工作流程
先看一张图,帮你理清整体逻辑:
流程其实不复杂。你想想看,核心就三步:创建通道、建立连接、收发数据。
发送文本消息
先来个最简单的——发文本。代码量很少,我直接贴出来:
// Peer A 端
const pc = new RTCPeerConnection();
const dc = pc.createDataChannel('chat', {
ordered: true // 保证顺序
});
dc.onopen = () => {
console.log('通道已打开');
dc.send('你好,这是第一条消息!');
};
dc.onmessage = (event) => {
console.log('收到回复:', event.data);
};
// Peer B 端
pc.ondatachannel = (event) => {
const dc = event.channel;
dc.onmessage = (e) => {
console.log('收到:', e.data);
dc.send('收到,over!');
};
};
你看,代码很简洁。send() 方法直接传字符串就行。onmessage 里拿到的 event.data 就是文本内容。
小技巧: 我建议在 onopen 回调里再开始发数据。别在通道还没准备好时就 send,会丢消息。曾经有个同事踩过这个坑,调试了半天才发现是时序问题。
发送文件数据
文件传输稍微复杂一点。因为文件是二进制数据,我们需要用 Blob 或 ArrayBuffer 来处理。
我的做法是:先把文件切成小块,分片发送。为什么?因为大文件一次性 send 会撑爆缓冲区。你想想看,一个 100MB 的文件直接塞进去,浏览器会崩溃的。
来看代码:
// 发送端
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
const chunkSize = 16 * 1024; // 16KB 一块
let offset = 0;
// 先发送文件元信息
dc.send(JSON.stringify({
type: 'meta',
name: file.name,
size: file.size
}));
// 分片发送
while (offset < file.size) {
const chunk = file.slice(offset, offset + chunkSize);
const buffer = await chunk.arrayBuffer();
dc.send(buffer);
offset += chunkSize;
// 可以加个进度回调
const progress = Math.round((offset / file.size) * 100);
console.log(`发送进度:${progress}%`);
}
// 发送完成标记
dc.send(JSON.stringify({ type: 'end' }));
});
// 接收端
let receivedChunks = [];
let fileMeta = null;
dc.onmessage = async (event) => {
const data = event.data;
// 判断是文本还是二进制
if (typeof data === 'string') {
const msg = JSON.parse(data);
if (msg.type === 'meta') {
fileMeta = msg;
console.log(`开始接收文件:${msg.name},大小:${msg.size}`);
} else if (msg.type === 'end') {
// 组装文件
const blob = new Blob(receivedChunks);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileMeta.name;
a.click();
URL.revokeObjectURL(url);
// 重置
receivedChunks = [];
fileMeta = null;
}
} else {
// 二进制数据,直接收集
receivedChunks.push(data);
}
};
注意: 分片大小不要太大。16KB 到 64KB 是比较安全的范围。我曾经试过用 1MB 的分片,结果在弱网环境下频繁丢包,重传搞得头大。
DataChannel 的配置选项
创建 DataChannel 时,可以传一个配置对象。我整理了一张表:
| 选项 | 类型 | 说明 |
|---|---|---|
| ordered | boolean | 是否保证顺序,默认 true |
| maxPacketLifeTime | number | 不可靠模式下,数据包存活时间(毫秒) |
| maxRetransmits | number | 不可靠模式下,最大重传次数 |
| protocol | string | 自定义子协议标识,默认空 |
举个例子。如果你要做实时游戏,不在乎丢几帧,那就用不可靠模式:
const dc = pc.createDataChannel('game', {
ordered: false,
maxRetransmits: 0 // 不重传
});
如果是文件传输,必须可靠:
const dc = pc.createDataChannel('file', {
ordered: true
});
避坑指南: 我曾经把 maxPacketLifeTime 和 maxRetransmits 同时设置了,结果通道创建失败。这两个参数是互斥的,只能选一个。文档里其实写了,但当时没仔细看,浪费了半天时间。
实际项目中的经验
最后分享几个我在项目中积累的经验:
- 通道数量:不要创建太多 DataChannel。每个通道都会占用资源。我一般控制在 3-5 个以内。
- 数据压缩:传输大文件前,建议先压缩。用 pako.js 做 gzip 压缩,能省不少带宽。
- 心跳检测:DataChannel 不会自动检测连接状态。我习惯每隔 5 秒发一个心跳包,如果连续 3 次没回应,就判定断开。
- 错误处理:send() 方法可能会抛出异常。比如通道关闭后还在发数据。记得用 try-catch 包一下。
嗯,DataChannel 的内容就这些。它虽然不像音视频那么炫酷,但在很多场景下非常实用。比如在线协作编辑、实时白板、文件共享等等。掌握了它,你的 WebRTC 技能树就又多了一个分支。