9、RTCDataChannel数据传输:创建DataChannel、发送文本消息、发送二进制数据(Blob/ArrayBuffer)、DataChannel的可靠与有序模式
聊完了音视频流,咱们来聊聊另一个很实用的东西——DataChannel。
说白了,DataChannel 就是在 WebRTC 的 P2P 连接上,开了一条「数据高速公路」。你可以在上面跑文本、跑文件、跑游戏指令,甚至跑控制信令。我最早接触它的时候,觉得这玩意儿简直就是浏览器里的 UDP/TCP 合体版。
嗯,今天我们就把它彻底搞明白。
9.1 创建 DataChannel:从连接中「拉」出一条数据通道
创建 DataChannel 有两种方式。一种是主动创建,另一种是在远端创建后,本地被动接收。
我个人习惯在 createOffer 之前就把 DataChannel 建好。为什么呢?因为这样 SDP 里会自动带上 DataChannel 的协商信息,省得后面再折腾。
createDataChannel(),接收方监听 ondatachannel 事件。
// 发起方:在创建 Offer 之前,先创建 DataChannel
const dataChannel = peerConnection.createDataChannel('myLabel', {
ordered: true,
maxRetransmits: 3
});
// 接收方:监听 ondatachannel 事件
peerConnection.ondatachannel = (event) => {
const receiveChannel = event.channel;
console.log('收到远端 DataChannel:', receiveChannel.label);
};
这里有个细节——label 只是个名字,你可以随便起。但如果你有多个 DataChannel,建议用有意义的名称,比如 chat、file-transfer、game-input。我在项目中就吃过亏,全叫 data,调试时根本分不清谁是谁。
9.2 发送文本消息:最基础的数据传输
DataChannel 发送文本,简单到令人发指。直接调用 send() 方法就行。
// 发送文本
dataChannel.send('你好,世界!');
// 发送 JSON 对象
const message = {
type: 'chat',
content: 'Hello WebRTC',
timestamp: Date.now()
};
dataChannel.send(JSON.stringify(message));
你可能会问:能直接发对象吗?不行。DataChannel 的 send() 只接受 string、Blob、ArrayBuffer 或 ArrayBufferView。所以发对象前,记得先 JSON.stringify。
JSON.parse 还原成对象。我习惯在消息里加一个 type 字段,这样接收方就能根据类型做不同处理。
9.3 发送二进制数据:Blob 与 ArrayBuffer
文本只是开胃菜。真正硬核的是二进制数据传输——比如传文件、传图片、传视频片段。
DataChannel 支持两种二进制格式:Blob 和 ArrayBuffer。怎么选?
- Blob:适合直接传文件。比如用户选了一个图片,你拿到
File对象(它继承自 Blob),直接send()出去。 - ArrayBuffer:适合需要精细控制二进制数据的场景。比如游戏中的坐标数据、自定义协议包。
// 发送 Blob(比如用户选择的文件)
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
dataChannel.send(file); // 直接发送 File 对象,它本身就是 Blob
});
// 发送 ArrayBuffer
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setInt32(0, 12345, true); // 小端序写入一个整数
view.setFloat32(4, 3.14, true); // 小端序写入一个浮点数
dataChannel.send(buffer);
接收端怎么区分是文本还是二进制?很简单,看 event.data 的类型。
dataChannel.onmessage = (event) => {
if (typeof event.data === 'string') {
console.log('收到文本:', event.data);
} else if (event.data instanceof Blob) {
console.log('收到 Blob,大小:', event.data.size);
// 可以转成 URL 展示图片
const url = URL.createObjectURL(event.data);
document.getElementById('image').src = url;
} else if (event.data instanceof ArrayBuffer) {
console.log('收到 ArrayBuffer,字节长度:', event.data.byteLength);
const view = new DataView(event.data);
const intVal = view.getInt32(0, true);
const floatVal = view.getFloat32(4, true);
console.log('解析数据:', intVal, floatVal);
}
};
9.4 可靠与有序模式:选 TCP 还是 UDP?
DataChannel 最灵活的地方在于,你可以自己选传输模式。它底层基于 SCTP 协议,但暴露给开发者的配置很简单——两个参数:ordered 和 maxRetransmits / maxPacketLifeTime。
| 模式 | ordered | maxRetransmits | maxPacketLifeTime | 行为描述 |
|---|---|---|---|---|
| 可靠有序 | true | 不设(或设大值) | 不设 | 类似 TCP,保证所有数据到达且顺序正确 |
| 可靠无序 | false | 不设(或设大值) | 不设 | 保证到达,但不保证顺序 |
| 不可靠有序 | true | 设有限值(如 3) | 不设 | 最多重传 3 次,超时则丢弃,但保证顺序 |
| 不可靠无序 | false | 不设 | 设有限值(如 500ms) | 数据包最多存活 500ms,超时丢弃,不保证顺序 |
你想想看,这四种模式分别适合什么场景?
- 可靠有序:文件传输、聊天消息。丢一条消息都不行,顺序也不能乱。
- 可靠无序:某些控制信令。消息必须到达,但顺序无所谓。
- 不可靠有序:实时游戏中的状态同步。可以丢几帧,但顺序不能乱。
- 不可靠无序:音视频数据(但音视频一般走 MediaStream,不走 DataChannel)。或者高频传感器数据,丢了就丢了,新的更重要。
9.5 核心逻辑流程图
下面这张图,把 DataChannel 的创建、发送、接收、模式选择串起来了。你可以把它当作本章的知识地图。
9.6 避坑指南
最后,分享几个我踩过的坑。
- DataChannel 状态检查:发送数据前,一定要检查
dataChannel.readyState是否为'open'。我曾经在connecting状态就发数据,结果消息丢了,排查了半天。 - 消息大小限制:前面提过,单条消息不要太大。浏览器实现不同,安全做法是每条消息不超过 16KB。大文件一定要分片。
- 关闭连接:不再使用 DataChannel 时,记得调用
dataChannel.close()。否则底层 SCTP 连接会一直占用资源。 - 二进制类型设置:可以通过
dataChannel.binaryType = 'arraybuffer'或'blob'来指定接收二进制数据的格式。默认是'blob'。如果你需要频繁操作二进制数据,建议设为'arraybuffer',性能更好。
公众号:蓝海资料掘金营,微信deep3321