10、数据通道:RTCDataChannel介绍、数据通道创建、文本数据传输、二进制数据传输
聊完了音视频,咱们来聊聊数据通道。
说实话,很多人一提到 WebRTC,第一反应就是视频通话。但在我实际的项目经验里,数据通道(RTCDataChannel) 的出场率一点都不比媒体流低。它就像一条隐藏在 P2P 连接里的“高速公路”,专门用来跑那些不需要实时编解码的数据。
你想想看,文件传输、游戏中的状态同步、聊天消息、甚至是一些控制信令,都可以走这条路。而且它最大的好处是——不经过服务器中转,延迟极低。
核心概念:RTCDataChannel 基于 SCTP 协议(流控制传输协议)构建,运行在 DTLS 加密通道之上。这意味着它天生就是加密的,而且支持可靠或不可靠两种传输模式。
10.1 数据通道的创建
创建数据通道其实很简单。你只需要一个已经建立好的 RTCPeerConnection 实例。
我个人习惯在 createOffer 之前就把通道建好,这样 offer 里会自动带上 data channel 的协商信息。当然,你也可以在连接建立后再创建,但那样需要额外的协商步骤。
// 创建数据通道
const dataChannel = peerConnection.createDataChannel('myChannel', {
ordered: true, // 保证消息顺序
maxRetransmits: 3 // 最大重传次数(可靠模式)
});
// 监听通道状态
dataChannel.onopen = () => {
console.log('数据通道已打开');
};
dataChannel.onclose = () => {
console.log('数据通道已关闭');
};
dataChannel.onerror = (error) => {
console.error('数据通道错误:', error);
};
dataChannel.onmessage = (event) => {
console.log('收到消息:', event.data);
};
这里有个细节要注意:maxRetransmits 和 maxPacketLifeTime 是互斥的。你只能选一个,或者都不选(那就是完全可靠模式)。
我曾经踩过的坑:在创建通道时同时设置了 maxRetransmits 和 maxPacketLifeTime,结果浏览器直接抛异常。后来查了规范才知道,这两个参数是二选一的。
10.2 文本数据传输
文本数据是最简单的。你直接调用 send() 方法就行。
// 发送文本消息
dataChannel.send('你好,这是文本消息');
// 发送 JSON 对象
const data = {
type: 'chat',
content: 'Hello WebRTC',
timestamp: Date.now()
};
dataChannel.send(JSON.stringify(data));
接收端收到的是 MessageEvent,里面的 data 属性就是字符串。如果你发的是 JSON,记得在接收端 JSON.parse() 一下。
嗯,这里要提醒一句:数据通道的单个消息大小有限制。不同浏览器不一样,Chrome 大概是 16KB 左右。超过这个大小,消息会被自动分片,但接收端会重新组装好再触发 onmessage。所以从 API 层面看,你不需要关心分片逻辑。
10.3 二进制数据传输
二进制数据才是数据通道真正发光的地方。文件、图片、视频片段,都可以通过二进制通道传输。
WebRTC 支持两种二进制类型:ArrayBuffer 和 Blob。我个人更推荐 ArrayBuffer,因为它在接收端处理起来更灵活。
// 发送 ArrayBuffer
const buffer = new ArrayBuffer(8);
const view = new Uint8Array(buffer);
view[0] = 0x48; // 'H'
view[1] = 0x65; // 'e'
// ... 填充数据
dataChannel.send(buffer);
// 发送 Blob(比如文件片段)
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
const chunkSize = 16 * 1024; // 16KB 分片
let offset = 0;
while (offset < file.size) {
const chunk = file.slice(offset, offset + chunkSize);
dataChannel.send(chunk);
offset += chunkSize;
}
});
接收端怎么判断是文本还是二进制?很简单,检查 event.data 的类型:
dataChannel.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) {
// 处理二进制数据
const view = new Uint8Array(event.data);
console.log('收到二进制数据,长度:', view.length);
} else if (event.data instanceof Blob) {
// 处理 Blob 数据
const reader = new FileReader();
reader.onload = () => {
console.log('Blob 内容:', reader.result);
};
reader.readAsText(event.data);
} else {
// 处理文本数据
console.log('收到文本:', event.data);
}
};
小技巧:如果你要传大文件,建议在应用层自己做分片和确认机制。虽然 SCTP 底层会分片,但应用层的分片可以让你实现断点续传和进度显示。
10.4 数据通道的配置选项
创建数据通道时,有几个关键参数值得你关注:
| 参数 | 类型 | 说明 |
|---|---|---|
| ordered | boolean | 是否保证消息顺序。true 表示按发送顺序接收,false 表示可以乱序 |
| maxPacketLifeTime | number | 消息在通道中的最大存活时间(毫秒),超时未送达则丢弃 |
| maxRetransmits | number | 最大重传次数,超过次数则丢弃消息 |
| protocol | string | 子协议标识,可以用来区分不同的数据通道用途 |
说白了,这些参数就是让你在可靠性和实时性之间做权衡。比如游戏中的位置同步,你可以用 ordered: false 加上 maxPacketLifeTime: 100,这样即使丢包了也无所谓,反正下一帧马上就来。
10.5 数据通道的生命周期
数据通道的状态变化其实很简单:
- connecting:通道正在建立
- open:通道已打开,可以收发数据
- closing:通道正在关闭
- closed:通道已关闭
当 RTCPeerConnection 断开时,所有关联的数据通道也会自动关闭。所以你不必手动一个个去关,但养成好习惯,在页面卸载时主动关闭通道总是没错的。
实战建议:在数据通道的 onclose 回调里,记得清理相关的 UI 状态和缓存数据。我曾经在一个项目中忘了做这件事,结果用户重新连接后,界面上还显示着旧的文件传输进度,搞得用户一头雾水。
10.6 知识体系图
下面这张图帮你理清数据通道的核心逻辑:
这张图把数据通道的核心要素都串起来了。从创建到配置,从数据类型到应用场景,每一步都有对应的知识点。你在实际开发中,可以把它当作一个检查清单,看看自己有没有遗漏什么。
好了,数据通道的内容就讲到这里。文本传输、二进制传输、配置选项,这些都是在实战中高频使用的技能。下一节我们会聊聊如何用数据通道实现一个完整的文件传输功能,到时候我会分享一些我在项目中用到的实用技巧。
最后一个小建议:如果你刚开始接触数据通道,建议先从文本传输入手,跑通了再尝试二进制。这样排查问题会容易很多。