6、数据通道进阶:可靠与不可靠模式、消息大小限制与分片、发送二进制数据(Blob/ArrayBuffer)
数据通道(DataChannel)是 WebRTC 里我最喜欢的部分。为什么?因为它让你在音视频之外,还能自由地传任何数据。文本、文件、游戏状态、实时指令……说白了,它就是一条浏览器之间的“TCP/UDP 二合一”管道。
但很多同学用 DataChannel 时,只停留在 send('hello') 的层面。一旦遇到大文件、二进制数据、或者需要控制传输可靠性时,就懵了。今天我们就来把这些硬骨头啃掉。
- 什么时候用可靠模式?什么时候用不可靠模式?
- 消息最大能发多大?超了怎么办?
- 二进制数据(Blob/ArrayBuffer)怎么发?
6.1 可靠与不可靠模式:选对模式,事半功倍
创建 DataChannel 时,有两个关键参数:ordered 和 maxRetransmits(或 maxPacketLifeTime)。它们决定了通道的行为。
| 模式 | ordered | maxRetransmits / maxPacketLifeTime | 行为 |
|---|---|---|---|
| 可靠、有序 | true(默认) | 不设置 | 类似 TCP:保证到达,保证顺序 |
| 不可靠、无序 | false | 设置其中一个 | 类似 UDP:可能丢包,不保证顺序 |
| 不可靠、有序 | true | 设置其中一个 | 保证顺序,但允许丢包(跳过丢失的) |
我个人习惯:传文件、关键指令用可靠模式;传实时位置、游戏状态用不可靠模式。你想想看,如果游戏里每个玩家的坐标都要重传,那延迟就爆炸了。
代码示例:创建两种通道
// 可靠通道:传文件
const fileChannel = peerConnection.createDataChannel('file-transfer', {
ordered: true
});
// 不可靠通道:传实时位置
const positionChannel = peerConnection.createDataChannel('position', {
ordered: false,
maxRetransmits: 0 // 不重传
});
maxRetransmits 和 maxPacketLifeTime 不能同时设置。前者限制重传次数,后者限制消息存活时间(毫秒)。选一个就行。
6.2 消息大小限制与分片:别想一次发太大
很多人以为 DataChannel 能随便发大消息。错!SCTP 协议底层有消息大小限制。不同浏览器不一样,但通常上限是 16KB 到 64KB 之间。
为什么会这样?因为 SCTP 把消息切成小块(chunk)传输,每个 chunk 最大 64KB。如果消息超过这个值,浏览器会直接报错或静默失败。
我记得有一次,用户上传一张 5MB 的图片,我用 channel.send(blob) 直接发,结果 Chrome 控制台报了个 Message too large。嗯,从那以后我再也不偷懒了。
分片策略:自己动手切
解决方案很简单:把大消息切成小块,每块单独发送,接收端再拼回去。
const CHUNK_SIZE = 16 * 1024; // 16KB 一块
function sendLargeMessage(channel, data) {
// data 是 ArrayBuffer
const totalChunks = Math.ceil(data.byteLength / CHUNK_SIZE);
// 先发送元信息
channel.send(JSON.stringify({
type: 'meta',
totalChunks: totalChunks,
totalSize: data.byteLength
}));
// 逐块发送
for (let i = 0; i < totalChunks; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, data.byteLength);
const chunk = data.slice(start, end);
channel.send(chunk);
}
}
接收端拼装
let receivedChunks = [];
let expectedChunks = 0;
let totalSize = 0;
channel.onmessage = (event) => {
if (typeof event.data === 'string') {
const meta = JSON.parse(event.data);
if (meta.type === 'meta') {
expectedChunks = meta.totalChunks;
totalSize = meta.totalSize;
receivedChunks = [];
}
} else {
// 二进制数据
receivedChunks.push(event.data);
if (receivedChunks.length === expectedChunks) {
// 拼装完成
const blob = new Blob(receivedChunks, { type: 'application/octet-stream' });
// 处理 blob...
}
}
};
6.3 发送二进制数据:Blob 与 ArrayBuffer
DataChannel 原生支持发送 string、Blob、ArrayBuffer 和 ArrayBufferView。但要注意:发送 Blob 时,浏览器会自动把它转成 ArrayBuffer,所以底层其实都是二进制。
我个人更推荐直接用 ArrayBuffer,因为控制更精细。比如你要发一个自定义协议的数据包,用 ArrayBuffer 可以精确控制字节偏移。
发送 ArrayBuffer
// 创建一个 4 字节的 ArrayBuffer
const buf = new ArrayBuffer(4);
const view = new Uint8Array(buf);
view[0] = 0x01; // 消息类型
view[1] = 0x00; // 保留
view[2] = 0x10; // 数据长度高位
view[3] = 0x20; // 数据长度低位
channel.send(buf);
发送 Blob
// 从文件输入获取 Blob
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
// 直接发送 Blob(但注意大小限制!)
// channel.send(file);
// 安全做法:先转成 ArrayBuffer,再分片
file.arrayBuffer().then(buffer => {
sendLargeMessage(channel, buffer);
});
});
6.4 知识体系图:数据通道进阶全景
6.5 总结
数据通道进阶,说白了就三件事:
- 选模式:可靠还是不可靠?看场景。别无脑用可靠,也别为了性能全用不可靠。
- 分片:消息有大小限制,超过 16KB 就自己切。先发元信息,再发数据块,接收端拼回去。
- 二进制:ArrayBuffer 是王道,Blob 是便利。大文件一定要分片,别偷懒。
嗯,这些坑我都踩过。希望你能少走弯路。