6、数据通道:RTCDataChannel的创建与配置、发送文本与二进制数据、文件传输实战
聊完了音视频,咱们来聊聊数据通道。
说实话,我刚开始接触 WebRTC 时,注意力全在视频通话上。后来才发现,RTCDataChannel 才是真正的宝藏。它让你在浏览器之间直接传数据,不需要经过服务器中转。延迟低、速度快,而且加密传输。
说白了,它就是一条浏览器之间的“专线”。
6.1 数据通道是什么?
RTCDataChannel 基于 SCTP 协议运行在 DTLS 加密之上。你可以把它想象成 WebSocket 的升级版——但不需要服务器。
它有两个核心模式:
- 可靠有序模式:类似 TCP,保证数据到达且顺序正确。适合文件传输。
- 不可靠无序模式:类似 UDP,数据可能丢包或乱序。适合游戏、实时消息。
关键点:数据通道和媒体流走的是同一个 ICE 连接。所以一旦 P2P 连接建立,你就能同时传音视频和数据。
6.2 创建与配置
创建数据通道有两种方式。我习惯在发起方直接创建,在接收方监听事件。
发起方创建
const dataChannel = peerConnection.createDataChannel('myChannel', {
ordered: true, // 是否保证顺序
maxRetransmits: 3, // 最大重传次数(不可靠模式用)
maxPacketLifeTime: 1000 // 最大存活时间(毫秒)
});
这里要注意:maxRetransmits 和 maxPacketLifeTime 不能同时设置。选一个就好。
接收方监听
peerConnection.ondatachannel = (event) => {
const dataChannel = event.channel;
console.log('收到数据通道:', dataChannel.label);
};
嗯,这里有个坑。我曾经在项目中忘记在发起方先调用 createDataChannel,结果接收方死活收不到 ondatachannel 事件。记住:发起方必须先创建,通道信息会通过 SDP 协商传递给对方。
6.3 发送文本与二进制数据
数据通道支持发送字符串、ArrayBuffer、Blob。接口非常简单:
// 发送文本
dataChannel.send('你好,WebRTC!');
// 发送二进制
const buffer = new ArrayBuffer(8);
const view = new Uint8Array(buffer);
view[0] = 0xFF;
dataChannel.send(buffer);
// 发送 Blob
const blob = new Blob(['Hello'], { type: 'text/plain' });
dataChannel.send(blob);
接收数据时,通过 onmessage 事件处理:
dataChannel.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) {
console.log('收到二进制数据,长度:', event.data.byteLength);
} else if (event.data instanceof Blob) {
console.log('收到 Blob,类型:', event.data.type);
} else {
console.log('收到文本:', event.data);
}
};
我的经验:发送大文件时,建议把数据切成小块(比如 16KB 一个 chunk)。我曾经一次性发送 10MB 的 ArrayBuffer,结果浏览器直接报错。后来改成切片发送,稳得很。
6.4 文件传输实战
咱们来写一个完整的文件传输示例。这个我做过好几次,踩过不少坑。
发送端代码
const CHUNK_SIZE = 16384; // 16KB
function sendFile(file, dataChannel) {
const reader = new FileReader();
let offset = 0;
reader.onload = (e) => {
dataChannel.send(e.target.result);
offset += e.target.result.byteLength;
if (offset < file.size) {
readSlice();
} else {
console.log('文件发送完成');
}
};
function readSlice() {
const slice = file.slice(offset, offset + CHUNK_SIZE);
reader.readAsArrayBuffer(slice);
}
readSlice();
}
接收端代码
let receivedBuffers = [];
dataChannel.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) {
receivedBuffers.push(event.data);
// 可以在这里更新进度条
}
};
// 当通道关闭时,合并所有数据
dataChannel.onclose = () => {
const totalLength = receivedBuffers.reduce((acc, buf) => acc + buf.byteLength, 0);
const combined = new Uint8Array(totalLength);
let offset = 0;
for (const buf of receivedBuffers) {
combined.set(new Uint8Array(buf), offset);
offset += buf.byteLength;
}
const blob = new Blob([combined]);
// 保存或展示文件
};
注意:接收端不能依赖 onclose 来判断文件传输完成。因为数据通道可能因为网络问题意外关闭。我建议在发送端发送一个特殊的“结束标记”,比如发送一个包含 { type: 'EOF', fileName: '...' } 的 JSON 消息。
6.5 知识体系图
下面这张图总结了数据通道的核心知识点:
6.6 避坑指南
最后分享几个我踩过的坑:
- 通道状态检查:发送数据前一定要检查
dataChannel.readyState === 'open'。我曾经在通道还没打开时就 send,结果数据丢了。 - 大文件切片:单次发送不要超过 64KB。浏览器底层有缓冲区限制,超了会报错。
- 连接断开处理:监听
onclose和onerror事件。P2P 连接随时可能断开,要做好重连或提示。 - SDP 协商顺序:创建数据通道必须在
createOffer之前。否则通道信息不会包含在 SDP 中。
我的习惯:在项目里我会封装一个 DataChannelManager 类,统一管理创建、发送、接收、重连逻辑。这样代码干净,也方便复用。
数据通道的潜力很大。除了文件传输,你还可以用它做实时白板、游戏同步、聊天消息。说白了,只要你能想到的实时数据传输场景,它都能胜任。