数据通道:RTCDataChannel 可靠与不可靠传输
数据通道:RTCDataChannel 可靠与不可靠传输
WebRTC 不止能传音视频,还能通过 DataChannel 传任意数据:文字消息、文件、游戏数据、白板指令。这篇讲 RTCDataChannel 的用法、可靠与不可靠模式、消息顺序、流量控制,以及实际应用场景。
大家好,我是黒漂技术佬。
提到 WebRTC,大家第一反应都是视频通话。但 WebRTC 还有一个很有用的东西——RTCDataChannel,在点对点连接上传输任意数据,和音视频走同一条通道。
聊天消息、文件传输、白板协作、游戏同步、远程控制……都能用 DataChannel 做。低延迟、P2P、不经过服务器(打洞成功的话)。
这篇讲 DataChannel 的用法、可靠/不可靠模式、顺序控制、常用场景。
一、RTCDataChannel 是什么?
作用
在 RTCPeerConnection 上建立数据通道,点对点传输任意二进制或文本数据。
特点
- 低延迟:P2P 直连,延迟比 WebSocket 还低
- 多种传输模式:可靠有序 / 不可靠无序,按需选择
- 任意数据:字符串、二进制、文件,什么都能传
- 加密:DTLS 加密,安全
- 和音视频复用连接:不用额外建连接
和 WebSocket 比
| RTCDataChannel | WebSocket | |
|---|---|---|
| 架构 | P2P(可能走TURN中继) | 客户端-服务器 |
| 延迟 | 低(直连) | 较高(经过服务器转发) |
| 传输模式 | 可靠/不可靠,有序/无序 | 可靠有序 |
| 服务器成本 | 低(只有信令) | 高(所有数据过服务器) |
| 穿透问题 | 需要处理 NAT | 没有 |
| 复杂度 | 高 | 低 |
适合实时性要求高、数据量大、不想走服务器的场景。
二、基本用法
创建 DataChannel
发起方创建
constdataChannel=pc.createDataChannel('my-channel',{ordered:true,// 保证顺序maxRetransmits:3// 最多重传3次});第一个参数是标签(channel name),第二个是配置。
接收方监听
pc.ondatachannel=(event)=>{constdataChannel=event.channel;dataChannel.onopen=()=>console.log('数据通道打开了');dataChannel.onmessage=(event)=>{console.log('收到消息:',event.data);};};发起方 createDataChannel 后,接收方会触发 ondatachannel 事件。
发送数据
// 发送字符串dataChannel.send('Hello WebRTC!');// 发送二进制constbuffer=newArrayBuffer(100);dataChannel.send(buffer);// 发送 BlobdataChannel.send(newBlob(['file data']));字符串、ArrayBuffer、Blob、TypedArray 都能发。
接收数据
dataChannel.onmessage=(event)=>{constdata=event.data;if(typeofdata==='string'){// 字符串console.log('文本消息:',data);}elseif(datainstanceofArrayBuffer){// 二进制console.log('二进制数据,长度:',data.byteLength);}};通道状态
dataChannel.readyState// 'connecting' | 'open' | 'closing' | 'closed'dataChannel.onopen=()=>{};dataChannel.onclose=()=>{};dataChannel.onerror=(err)=>{};关闭通道
dataChannel.close();三、可靠 vs 不可靠,有序 vs 无序
DataChannel 有四个模式,根据场景选:
两个关键参数
ordered
true:消息按发送顺序到达(有序)false:消息可能乱序到达(无序)
maxRetransmits / maxPacketLifeTime
- 设置了就是可靠模式(丢了重传)
- 都不设(null)就是不可靠模式(丢了就丢了)
四种组合
1. 可靠有序(默认)
{ordered:true}- 和 TCP 类似,保证可靠、保证顺序
- 丢包会重传,队头阻塞
- 适合:聊天消息、文件传输、信令
2. 可靠无序
{ordered:false,maxRetransmits:3}- 保证送达,但不保证顺序
- 比有序快一点,不会队头阻塞
- 适合:消息之间没有依赖关系的场景
3. 不可靠有序(很少用)
{ordered:true,maxRetransmits:0}- 保证顺序,但丢了不重传
- 用得少
4. 不可靠无序(类似 UDP)
{ordered:false,maxRetransmits:0}- 不保证送达,不保证顺序
- 延迟最低,最快
- 适合:实时游戏状态、鼠标位置、传感器数据
怎么选?
| 场景 | 模式 | 原因 |
|---|---|---|
| 文字聊天 | 可靠有序 | 消息不能丢,顺序不能乱 |
| 文件传输 | 可靠有序 | 数据必须完整,顺序不能错 |
| 游戏同步 | 不可靠无序 | 实时最重要,旧数据丢了没关系 |
| 鼠标位置/白板笔画 | 不可靠有序 | 顺序重要,丢几个点没事 |
| 实时传感器数据 | 不可靠无序 | 最新的最重要,旧的丢了无所谓 |
四、常用配置参数
完整配置
constchannel=pc.createDataChannel('channel-name',{ordered:true,// 是否有序maxPacketLifeTime:100,// 重传最长时间(毫秒),和maxRetransmits二选一maxRetransmits:3,// 最大重传次数protocol:'',// 子协议,自定义negotiated:false,// 是否预先协商好id:0// 通道ID,negotiated为true时指定});maxPacketLifeTime vs maxRetransmits
两个都是控制可靠程度的,二选一:
maxPacketLifeTime:最多重传多久,时间限制maxRetransmits:最多重传几次,次数限制
都不设就是完全不可靠。
negotiated 模式
默认 false:一方 create,另一方 ondatachannel 接收。
设为 true:两边约定好 id,各自 create,不用等对方事件。适合提前知道通道数量的场景。
五、流量控制与缓冲
发送缓冲
发送太快的话,数据会存在缓冲区里。
bufferedAmount
console.log('缓冲区字节数:',dataChannel.bufferedAmount);bufferedAmountLowThreshold
缓冲区低于这个值时触发 bufferedamountlow 事件:
dataChannel.bufferedAmountLowThreshold=1024*1024;// 1MBdataChannel.onbufferedamountlow=()=>{// 缓冲区低于阈值了,可以继续发了sendMoreData();};发送大文件的做法
不能一次性全发,会撑爆缓冲区。分片发送,控制速度:
constCHUNK_SIZE=16*1024;// 每片16KBletoffset=0;functionsendFile(file){functionsendNext(){while(offset<file.size&&dataChannel.bufferedAmount<1024*1024){constchunk=file.slice(offset,offset+CHUNK_SIZE);dataChannel.send(chunk);offset+=CHUNK_SIZE;}if(offset<file.size){// 缓冲区满了,等有空间了再发dataChannel.onbufferedamountlow=sendNext;}else{console.log('文件发送完成');}}sendNext();}六、多通道
一个 PeerConnection 可以建多个 DataChannel,各干各的:
// 聊天通道(可靠有序)constchatChannel=pc.createDataChannel('chat',{ordered:true});// 游戏数据通道(不可靠无序)constgameChannel=pc.createDataChannel('game',{ordered:false,maxRetransmits:0});// 文件传输通道(可靠有序,单独一条不影响聊天)constfileChannel=pc.createDataChannel('file',{ordered:true});不同通道互不干扰。比如传大文件的时候,聊天消息不会被卡住。
七、实际应用场景
场景 1:文字聊天
// 发送chatChannel.send(JSON.stringify({type:'message',user:'张三',text:'你好',time:Date.now()}));// 接收chatChannel.onmessage=(e)=>{constmsg=JSON.parse(e.data);displayMessage(msg);};P2P 聊天,消息不经过服务器,隐私好,延迟低。
场景 2:文件传输
分片发送,带元信息:
// 发送方:先发文件信息,再发数据fileChannel.send(JSON.stringify({type:'file-info',name:'photo.jpg',size:file.size}));// 然后分片发文件内容...// 接收方:收完信息收数据,最后拼起来letfileData=[];fileChannel.onmessage=(e)=>{if(typeofe.data==='string'){constinfo=JSON.parse(e.data);// 记录文件信息}else{fileData.push(e.data);// 收完了拼起来下载}};P2P 传文件,不占服务器带宽,速度快(直连的话)。
场景 3:实时游戏同步
// 发送玩家位置(不可靠无序,快就行)functionsendPosition(x,y){constdata=newFloat32Array([x,y]);gameChannel.send(data);}// 接收,更新位置gameChannel.onmessage=(e)=>{const[x,y]=newFloat32Array(e.data);updatePlayerPosition(x,y);};低延迟,适合快节奏游戏。
场景 4:白板协作
// 发送画笔轨迹whiteboardChannel.send(JSON.stringify({type:'draw',x:100,y:200,color:'#ff0000'}));配合视频通话,远程教学、协作办公。
场景 5:远程控制
发送鼠标键盘事件,远程控制对方电脑。
八、和音视频配合
DataChannel 和音视频走同一个 PeerConnection,不用额外建连:
constpc=newRTCPeerConnection(config);// 加音视频stream.getTracks().forEach(t=>pc.addTrack(t,stream));// 加数据通道constchatChannel=pc.createDataChannel('chat');一个连接搞定所有:视频、音频、聊天、文件。
信令也可以走 DataChannel
连接建立之后,后续的控制消息(比如静音、开关摄像头)可以走 DataChannel,不用走信令服务器了。
九、常见坑
坑 1:数据太大发不出去
单次 send 不能太大,浏览器有限制(一般 64KB 左右)。大文件一定要分片。
坑 2:发太快缓冲区爆了
不看 bufferedAmount 一股脑发,会内存暴涨,甚至丢数据。要做流量控制。
坑 3:通道没打开就发消息
readyState 不是 open 的时候 send 会报错。要等 onopen 之后再发。
坑 4:不可靠模式真的会丢
别在不可靠通道传重要数据,丢了就是丢了,不会重传。
坑 5:不同浏览器支持差异
老浏览器可能不支持某些参数(比如 maxPacketLifeTime)。用 adapter.js + 特性检测。
坑 6:二进制和字符串混着发
接收方要判断 data 类型,字符串和二进制处理方式不一样。
十、性能考量
延迟
- P2P 直连:延迟很低,同城几十毫秒
- 走 TURN 中继:延迟高一些,看 TURN 服务器位置
吞吐量
直连的话,带宽取决于两端的上行/下行速度,能跑满带宽。
走 TURN 的话,受限于 TURN 服务器的带宽。
多少条通道合适?
- 不同用途分开建通道,互不影响
- 但也别建太多,有开销
- 一般 2-5 条足够:聊天、文件、游戏数据……
十一、本篇小结
- RTCDataChannel 在 PeerConnection 上传输任意数据,P2P、低延迟
- 四种模式:可靠有序(默认)、可靠无序、不可靠有序、不可靠无序
- 可靠有序适合聊天、文件传输;不可靠无序适合游戏、实时数据
- 大文件要分片发送,注意流量控制(bufferedAmount)
- 一个连接可以建多个 DataChannel,不同用途分开
- 常见场景:文字聊天、文件传输、游戏同步、白板协作、远程控制
- 和音视频复用同一个连接,不用额外建连
下一篇讲信令服务器设计与实现:怎么搭一个 WebRTC 的信令服务。
我是黒漂技术佬。