19、DataChannel:创建、模式选择与文件传输实战
聊完了音视频,咱们来点不一样的。
WebRTC 不只是能传视频和音频。它还能传文本、传文件、传任何你想传的二进制数据。这个能力,就叫 DataChannel。
我刚开始接触 WebRTC 时,以为它就是个视频通话库。后来发现 DataChannel 的存在,说实话,挺惊喜的。它让你在 P2P 链路上跑任意数据,延迟极低,还不用经过服务器中转。
19.1 DataChannel 是什么?
DataChannel 是 WebRTC 提供的一个 API。它建立在 SCTP(Stream Control Transmission Protocol)协议之上。你可以把它理解成一条「数据高速公路」,和音视频轨道并行。
它有两个核心特点:
- P2P 直连:数据不经过服务器,直接点对点传输。
- 可配置可靠性:你可以选择「可靠模式」或「不可靠模式」。
我在项目中遇到过不少团队,把 DataChannel 当 WebSocket 用。其实它比 WebSocket 更底层、更灵活。WebSocket 必须经过服务器中转,DataChannel 是端到端的。
一句话总结:DataChannel = 浏览器里的 UDP/TCP 选择权。
19.2 创建 DataChannel
创建 DataChannel 很简单。你只需要一个 RTCPeerConnection 实例。
// 创建 RTCPeerConnection
const pc = new RTCPeerConnection();
// 创建 DataChannel
const dataChannel = pc.createDataChannel('myLabel', {
ordered: true,
maxRetransmits: 3
});
这里有两个关键参数:
- label:通道名称,随便起。我习惯用 'chat' 或 'fileTransfer'。
- options:配置对象,决定通道的行为。
options 里最重要的两个属性:
| 属性 | 说明 | 默认值 |
|---|---|---|
| ordered | 是否保证消息顺序 | true |
| maxRetransmits | 最大重传次数(不可靠模式) | 无限制 |
| maxPacketLifeTime | 消息存活时间(毫秒,不可靠模式) | 无限制 |
嗯,这里要注意:maxRetransmits 和 maxPacketLifeTime 不能同时设置。你只能选一个。
19.3 可靠模式 vs 不可靠模式
说白了,这就是 TCP 和 UDP 的区别。
可靠模式:
- 保证消息到达,保证顺序。
- 适合文本聊天、文件传输。
- 设置方式:不设置 maxRetransmits 和 maxPacketLifeTime。
不可靠模式:
- 消息可能丢失,顺序可能乱。
- 适合游戏状态同步、实时位置更新。
- 设置方式:设置 maxRetransmits 或 maxPacketLifeTime。
我的建议:如果你不确定用哪种,先用可靠模式。等遇到性能瓶颈再切不可靠。我曾经在一个实时协作项目中,一开始用了不可靠模式,结果文档内容经常对不上。后来改成可靠模式,问题就解决了。
19.4 发送文本数据
发送文本是最简单的。DataChannel 原生支持字符串。
// 发送文本
dataChannel.send('Hello, WebRTC!');
// 接收文本
dataChannel.onmessage = (event) => {
console.log('收到消息:', event.data);
};
就这么简单。你甚至可以把它当聊天室用。
但要注意:send() 方法只能在通道状态为 open 时调用。否则会抛异常。
dataChannel.onopen = () => {
console.log('通道已打开');
dataChannel.send('可以发消息了');
};
我见过有人忘了监听 onopen,直接调用 send,结果报错。嗯,这个坑我踩过。
19.5 发送二进制数据
二进制数据稍微复杂一点。DataChannel 支持 Blob 和 ArrayBuffer。
// 发送 ArrayBuffer
const buffer = new ArrayBuffer(8);
const view = new Uint8Array(buffer);
view[0] = 42;
dataChannel.send(buffer);
// 发送 Blob
const blob = new Blob(['Hello'], { type: 'text/plain' });
dataChannel.send(blob);
接收端需要判断数据类型:
dataChannel.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) {
console.log('收到二进制数据');
} else if (event.data instanceof Blob) {
console.log('收到 Blob');
} else {
console.log('收到文本:', event.data);
}
};
你想想看,有了这个能力,你能做什么?
- 传输图片、视频片段
- 传输序列化的 JSON 数据
- 传输游戏中的状态快照
19.6 文件传输实战
文件传输是 DataChannel 的经典场景。咱们来写一个完整的例子。
核心思路:
- 用户选择文件
- 将文件分片(chunk)
- 通过 DataChannel 逐片发送
- 接收端组装文件
为什么要分片?因为 DataChannel 的单条消息大小有限制。不同浏览器不一样,Chrome 大约是 16KB。我建议每片 16KB 以内。
// 发送端
const CHUNK_SIZE = 16 * 1024; // 16KB
function sendFile(file) {
const reader = new FileReader();
let offset = 0;
reader.onload = (e) => {
const chunk = e.target.result;
dataChannel.send(chunk);
offset += chunk.byteLength;
if (offset < file.size) {
readNextChunk();
} else {
console.log('文件发送完成');
}
};
function readNextChunk() {
const slice = file.slice(offset, offset + CHUNK_SIZE);
reader.readAsArrayBuffer(slice);
}
readNextChunk();
}
接收端:
// 接收端
let receivedChunks = [];
let expectedSize = 0;
dataChannel.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) {
receivedChunks.push(event.data);
}
};
// 当收到文件元信息时(比如文件名、大小)
function onFileMeta(meta) {
expectedSize = meta.size;
receivedChunks = [];
}
// 所有分片收完后,组装文件
function assembleFile() {
const blob = new Blob(receivedChunks);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'received_file';
a.click();
}
注意:实际项目中,你还需要发送文件元信息(文件名、大小、分片总数)。我通常先发一个 JSON 字符串作为「文件头」,然后再发二进制分片。接收端根据文件头判断何时组装。
19.7 知识体系图
下面这张图总结了 DataChannel 的核心逻辑:
19.8 避坑指南
最后,分享几个我踩过的坑:
- 通道状态检查:发送前一定要检查
dataChannel.readyState === 'open'。我曾经在通道还没打开时就发数据,结果数据丢了。 - 分片大小:不要超过 16KB。有些浏览器限制更小,我习惯用 8KB 保底。
- 二进制 vs 文本:接收端一定要做类型判断。我见过有人把 ArrayBuffer 当字符串处理,结果乱码。
- 文件传输进度:记得给用户反馈。我通常用
offset / file.size计算百分比。
DataChannel 是个好东西。它让 WebRTC 不再只是「视频通话工具」,而是一个真正的 P2P 数据通道。你可以用它做很多事情:文件分享、实时协作、游戏联机……
嗯,这一章就到这里。代码不多,但思路很重要。下次你写 P2P 应用时,记得 DataChannel 这个选项。