数据通道与文件传输:RTCDataChannel API、可靠/不可靠模式、二进制数据传输、P2P文件分享实现
聊到WebRTC,很多人第一反应就是视频通话。但说实话,数据通道(DataChannel)才是让我觉得最兴奋的部分。它让你在浏览器之间直接建立一条数据管道,不需要经过任何服务器中转。我最早接触这个功能时,第一反应就是——这不就是浏览器版的P2P文件传输吗?
今天我们就来彻底搞懂DataChannel。从API用法到可靠模式的选择,再到怎么传二进制数据,最后实现一个完整的P2P文件分享工具。
RTCDataChannel API:浏览器里的Socket
DataChannel的API设计得很简洁。你只需要一个RTCPeerConnection实例,然后调用它的createDataChannel()方法就行。我个人习惯把DataChannel看作是WebSocket的P2P版本——只不过WebSocket需要服务器中转,而DataChannel是端到端的。
// 创建数据通道
const peerConnection = new RTCPeerConnection(config);
const dataChannel = peerConnection.createDataChannel('myChannel', {
ordered: true,
maxRetransmits: 3
});
// 监听通道打开
dataChannel.onopen = () => {
console.log('数据通道已打开');
dataChannel.send('Hello from peer!');
};
// 监听消息
dataChannel.onmessage = (event) => {
console.log('收到消息:', event.data);
};
// 监听关闭
dataChannel.onclose = () => {
console.log('数据通道已关闭');
};
这里有个细节要注意:createDataChannel()必须在发起连接的那一端调用。在应答端,你需要监听ondatachannel事件来获取通道对象。我在项目中遇到过有人两边都调用了createDataChannel(),结果创建了两个通道,互相发消息都收不到。
关键点:DataChannel的创建是单向的。发起方调用createDataChannel(),应答方监听ondatachannel事件。两边不要重复创建。
可靠模式 vs 不可靠模式:选对路子
DataChannel支持两种传输模式,这直接决定了你的数据怎么到达对端。我刚开始用的时候也纠结过,后来发现其实选择标准很简单。
| 特性 | 可靠模式 | 不可靠模式 |
|---|---|---|
| 数据顺序 | 保证有序到达 | 可能乱序 |
| 丢包重传 | 自动重传直到成功 | 不重传或有限重传 |
| 延迟 | 较高(等待重传) | 低(丢包就跳过) |
| 适用场景 | 文件传输、文本聊天 | 游戏状态、实时数据流 |
可靠模式说白了就是TCP那套机制——保证数据不丢、不乱序。适合传文件、聊天消息这类不能出错的数据。不可靠模式则像UDP,数据丢了就丢了,不重传。适合游戏中的位置更新、实时股价推送这类可以容忍少量丢包但对延迟敏感的场景。
我曾经做过一个在线协作白板项目,一开始用的可靠模式。结果画笔画到一半,网络抖动了一下,所有笔画都卡住了——因为后面的数据在等前面丢包重传。后来改成不可靠模式,配合本地状态同步,体验就好多了。
我的建议:如果你不确定选哪个,先问自己一个问题——数据丢了会怎样?如果会出大问题(比如文件损坏),用可靠模式。如果只是画面闪一下,用不可靠模式。
二进制数据传输:不只是文本
DataChannel默认传的是字符串,但实际场景中我们经常需要传二进制数据。比如文件、图片、音频片段。WebRTC支持两种二进制格式:ArrayBuffer和Blob。
// 发送二进制数据
const buffer = new ArrayBuffer(1024);
const view = new Uint8Array(buffer);
view[0] = 0xFF; // 随便写点数据
dataChannel.send(buffer);
// 接收二进制数据
dataChannel.binaryType = 'arraybuffer'; // 或 'blob'
dataChannel.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) {
const bytes = new Uint8Array(event.data);
console.log('收到二进制数据,长度:', bytes.length);
}
};
这里有个坑:binaryType必须在接收消息之前设置。我见过有人收到消息后才设置,结果数据已经被当作字符串处理了,全变成了乱码。嗯,这个坑我踩过。
另外要注意的是,DataChannel对单条消息的大小有限制。Chrome默认是16KB,超过这个大小会报错。传大文件时,你需要自己分片发送。
注意:单条消息大小不要超过16KB(SCTP协议限制)。传大文件必须分片,每片控制在16KB以内。
P2P文件分享实现:从理论到代码
好了,理论说完了,我们来写一个真正的P2P文件分享工具。核心思路很简单:把文件切成小块,通过DataChannel一块一块发过去,接收端再拼起来。
先看发送端的核心逻辑:
// 发送端:分片发送文件
function sendFile(file, dataChannel) {
const chunkSize = 16 * 1024; // 16KB
let offset = 0;
// 先发送文件元信息
const metadata = {
name: file.name,
size: file.size,
type: file.type,
totalChunks: Math.ceil(file.size / chunkSize)
};
dataChannel.send(JSON.stringify(metadata));
// 逐片发送
const reader = new FileReader();
reader.onload = (e) => {
dataChannel.send(e.target.result);
offset += chunkSize;
if (offset < file.size) {
readNextChunk();
} else {
console.log('文件发送完成');
}
};
function readNextChunk() {
const slice = file.slice(offset, offset + chunkSize);
reader.readAsArrayBuffer(slice);
}
readNextChunk();
}
接收端这边,需要先解析元信息,然后接收分片并重组:
// 接收端:接收并重组文件
let receivedChunks = [];
let fileMetadata = null;
let receivedSize = 0;
dataChannel.onmessage = (event) => {
if (typeof event.data === 'string') {
// 这是元信息
fileMetadata = JSON.parse(event.data);
receivedChunks = new Array(fileMetadata.totalChunks);
console.log('准备接收文件:', fileMetadata.name);
return;
}
// 这是二进制数据块
const chunkIndex = Math.floor(receivedSize / (16 * 1024));
receivedChunks[chunkIndex] = event.data;
receivedSize += event.data.byteLength;
// 更新进度
const progress = (receivedSize / fileMetadata.size) * 100;
console.log(`接收进度: ${progress.toFixed(1)}%`);
// 检查是否接收完成
if (receivedSize >= fileMetadata.size) {
assembleFile();
}
};
function assembleFile() {
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);
}
这个实现虽然简单,但已经能用了。实际项目中你还需要考虑:断点续传、错误重试、进度回调、多文件并发传输等。不过核心骨架就是这个样子。
核心流程:发送端分片 → DataChannel传输 → 接收端重组。元信息用JSON字符串传,文件数据用ArrayBuffer传。
知识体系总览
下面这张图把DataChannel的核心知识点串起来了。从API创建到模式选择,再到二进制传输和文件分享,每一步都有对应的技术要点。
从这张图可以看得很清楚:API是基础,模式选择决定了传输行为,二进制传输解决了数据类型问题,最终汇聚到P2P文件分享这个实际应用上。每一步都有对应的技术细节和注意事项。
最后说一句,DataChannel的能力远不止文件传输。你可以用它做实时游戏同步、协作编辑、远程桌面控制……只要你能想到的P2P数据交换场景,它都能胜任。而且它天然支持加密(DTLS),安全性有保障。我个人觉得,DataChannel是WebRTC里被低估的一个功能,值得花时间深入研究。