第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('数据通道已关闭');
};
小技巧:如果你不设置maxRetransmits或maxPacketLifeTime,默认就是可靠模式。我个人习惯在创建时明确指定,避免混淆。
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的核心脉络。
7.7 避坑指南
最后,分享几个我踩过的坑。
- 消息大小限制:不同浏览器限制不同。Chrome单条最大256KB,Firefox只有16KB。传大文件必须分片。
- 通道状态检查:发送数据前,一定要检查
dataChannel.readyState是否为'open'。否则会抛异常。 - 并发发送:DataChannel内部有流控,但如果你连续快速发送大量小消息,可能会触发背压。建议用队列控制发送节奏。
- 关闭时机:不要在
onmessage里直接关闭通道。我曾经在接收完最后一片数据后立刻关闭,结果导致部分数据丢失。加个setTimeout延迟关闭会安全很多。
嗯,数据通道的内容就这些。它不复杂,但很实用。你想想看,浏览器之间直接传文件,不需要服务器,不需要插件,原生支持——这在几年前简直不敢想。现在,一行代码就能搞定。