WebRTC 全景实战 (6):Data Channel 与 SCTP over DTLS
"媒体走 RTP,数据走 SCTP——WebRTC 用两条逻辑通道完成实时通信的全部载荷。"
WebRTC 三大核心 API:getUserMedia(Ch1 采集媒体)、RTCPeerConnection(Ch2 建立连接)、RTCDataChannel(本章 传输任意数据)。Data Channel 让你在已建立的 DTLS 加密通道 上,以 P2P 方式传输文本、二进制、文件——无需额外服务器中转。
与 MBONE 多播时代「一份数据广播给所有人」不同(见 Curious — 历史),Data Channel 是单播 P2P 模型:每个 PeerConnection 只有两端,数据经 ICE 选出的最优路径直达对端——或经 TURN 中继(Ch5)。Ron Frederick 曾设想用 RTP + IP 多播做文件传输——「原始的做种者可以立即将多播流发送到所有接收者」——但 WebRTC 选择了更务实的路径:在已建立的 DTLS 通道上用 SCTP 做可靠/部分可靠的消息传输。
本章覆盖 RTCDataChannel API、SCTP over DTLS 协议栈、DCEP 建立协议、有序/无序传输、背压控制、文件分片传输,以及与 WebSocket 的架构对比。
配套代码:examples/webrtc-lab/client/ch06-data-channel/
本篇术语表
| 术语 | 英文 | 解释 |
|---|---|---|
| RTCDataChannel | — | WebRTC 的数据传输 API,在 PeerConnection 上创建逻辑数据通道 |
| SCTP | Stream Control Transmission Protocol | 面向消息的传输协议,支持多流、有序/无序、部分可靠性 |
| DTLS | Datagram Transport Layer Security | 基于 UDP 的 TLS,WebRTC 的加密层,见 Ch7 |
| SCTP over DTLS | — | RFC 8832,SCTP 载荷封装在 DTLS 记录中 |
| DCEP | Data Channel Establishment Protocol | RFC 8832 §6,SCTP 上建立 Data Channel 的控制协议 |
| ordered | — | 是否保证消息按发送顺序到达 |
| maxRetransmits | — | 最大重传次数,0 表示不重传(类 UDP) |
| maxPacketLifeTime | — | 消息最大存活时间(毫秒),超时丢弃 |
| binaryType | — | 接收二进制消息时的 JS 类型:blob 或 arraybuffer |
| bufferedAmount | — | 尚未发送完成的字节数,用于背压控制 |
| negotiated | — | 是否由 SDP 协商创建(而非 createDataChannel) |
| label | — | Data Channel 的人类可读名称,用于区分多个通道 |
| id | — | Data Channel 的数字标识符(0-65534),多通道时必填 |
| PPID | Payload Protocol Identifier | SCTP 层标识消息类型(字符串/二进制/空) |
| partial reliability | 部分可靠性 | SCTP 允许配置「最多重传 N 次」或「最多存活 T 毫秒」 |
| usrsctp | — | 用户空间 SCTP 实现,WebRTC 浏览器内核使用 |
| stream id | SCTP Stream ID | SCTP 流标识,每个 Data Channel 映射到一个 stream |
| DataChannel 状态 | — | connecting → open → closing → closed |
一、协议栈:SCTP over DTLS over ICE
Data Channel 不是独立的 UDP 套接字,而是嵌套在 WebRTC 协议栈中:
与 SRTP 媒体通道的关系:
Data Channel 与 SRTP 共享同一个 DTLS 会话和 ICE 传输。ICE 连通(Ch5)后,DTLS 握手完成,SCTP 关联建立,Data Channel 才能 open。因此 dc.onopen 通常晚于 iceConnectionState=connected。
1.1 为什么选 SCTP 而非 TCP
| 特性 | TCP | SCTP | WebRTC 需求 |
|---|---|---|---|
| 消息边界 | 字节流,需自行分帧 | 原生消息边界 | 游戏状态、文件分片 |
| 多路复用 | 一个连接一个流 | 多 stream | 多个 Data Channel |
| 队头阻塞 | 有 | 按 stream 隔离 | 文件传输不阻塞聊天 |
| 部分可靠性 | 无 | 可配置 | 位置更新可丢包 |
| NAT 友好 | 需额外封装 | 封装在 DTLS/UDP 中 | 与 ICE 兼容 |
WebRTC 没有在 UDP 上直接跑 TCP(如 TCP-over-UDP),而是选择了 SCTP——IETF 为 WebRTC 专门定义了 SCTP over DTLS(RFC 8831、RFC 8832)。
二、SCTP 关联建立与 DCEP
2.1 SCTP 四路握手
DTLS 握手完成后,双方 SCTP 栈交换 INIT / INIT-ACK / COOKIE-ECHO / COOKIE-ACK,建立 SCTP 关联(association):
浏览器的 SCTP 实现基于 usrsctp(用户空间 SCTP 库),由 WebRTC 原生层封装,JavaScript 层只看到 RTCDataChannel API。
SCTP 公共头(RFC 4960 §3.1):
2.2 DCEP:Data Channel Establishment Protocol
Data Channel 不是 SCTP 关联建立就自动可用——需要 DCEP 在 SCTP 上协商每个 channel 的参数:
| DCEP 消息 | 方向 | 内容 |
|---|---|---|
| OPEN | 发起方 → 应答方 | label、protocol、ordered、maxRetransmits/maxPacketLifeTime、stream id |
| ACK | 应答方 → 发起方 | 确认 channel 建立 |
发起方调用 createDataChannel("chat") 后,浏览器在 SCTP 上自动发送 DCEP OPEN;应答方 ondatachannel 触发,回复 ACK 后双方 readyState 变为 open。
DCEP OPEN 固定头(RFC 8832 §6.1),后跟可变长 Label 与 Protocol 字符串:
三、为什么需要 Data Channel
| 通道 | 路径 | 延迟 | 典型用途 |
|---|---|---|---|
| WebSocket | 客户端 ↔ 服务器 | 取决于服务器位置 | 信令、聊天、推送 |
| HTTP/REST | 客户端 ↔ 服务器 | 高(请求-响应) | 文件上传、API |
| Data Channel | P2P(或 TURN 中继) | 最低 | 游戏状态、白板、文件、传感器 |
Data Channel 的核心价值:
- 零服务器中转:数据直达对端,服务器带宽成本为零
- 与媒体同步:游戏/协作场景中,状态更新与音视频在同一连接上
- 灵活可靠性:可按消息配置有序/无序、可靠/部分可靠
- 多路复用:一个 PeerConnection 上可开多个 Data Channel(如 chat + file + control)
Curious 历史 中 Ron Frederick 的 Spacewar 多播游戏是 Data Channel 的远古祖先——多个客户端广播飞船位置,所有人实时看到彼此。今天 Data Channel 用单播 SCTP 实现了类似效果,但不需要 MBONE 多播网络。
四、RTCDataChannel API 详解
4.1 发起方:createDataChannel
const pc = new RTCPeerConnection({ iceServers: ICE_SERVERS });
const dc = pc.createDataChannel("chat", {
ordered: true,
maxRetransmits: undefined,
protocol: "",
negotiated: false,
id: undefined,
});
dc.onopen = () => {
console.log("Data Channel open, readyState:", dc.readyState);
dc.send("Hello P2P!");
};
dc.onclose = () => console.log("Data Channel closed");
dc.onerror = (e) => console.error("Data Channel error:", e);
dc.onmessage = (event) => {
console.log("Received:", event.data, typeof event.data);
};
4.2 应答方:ondatachannel
pc.ondatachannel = (event) => {
const channel = event.channel;
console.log("Incoming channel:", channel.label, channel.id);
channel.onopen = () => console.log("Remote-initiated channel open");
channel.onmessage = (e) => console.log("Message:", e.data);
};
4.3 完整建立时序
必须在 createOffer() 之前 调用 createDataChannel(),否则 SDP 中不会包含 SCTP 协商信息,对端收不到 Data Channel。
4.4 在 ch02 基础上添加 Data Channel
基于 examples/webrtc-lab/client/ch02-p2p-basic/,最小改动即可支持 Data Channel:
function createPeerConnection() {
pc = new RTCPeerConnection({ iceServers: ICE_SERVERS });
// 仅 Caller 创建 Data Channel
if (role === "caller") {
dc = pc.createDataChannel("chat");
dc.onopen = () => console.log("DC open");
dc.onmessage = (e) => console.log("DC msg:", e.data);
}
pc.ondatachannel = (e) => {
dc = e.channel;
dc.onopen = () => console.log("DC open (remote-initiated)");
dc.onmessage = (ev) => console.log("DC msg:", ev.data);
};
// ... 其余 ICE / track 逻辑不变
}
五、传输模式:有序/无序与可靠性
SCTP 的核心优势是按消息(message-oriented) 而非按字节流(TCP),且每个消息可独立配置可靠性:
| 配置 | 行为 | 类比 | 适用场景 |
|---|---|---|---|
ordered: true(默认) | 保证顺序 | TCP | 聊天、文件传输 |
ordered: false | 不保证顺序 | — | 游戏位置更新(只要最新) |
maxRetransmits: 0 | 不重传 | UDP | 实时传感器、心跳 |
maxRetransmits: 3 | 最多重传 3 次 | 部分可靠 | 可容忍偶尔丢包的状态 |
maxPacketLifeTime: 1000 | 1 秒内未送达则丢弃 | — | 实时性优先于完整性 |
const chat = pc.createDataChannel("chat", { ordered: true });
const position = pc.createDataChannel("position", {
ordered: false,
maxRetransmits: 0,
});
const draw = pc.createDataChannel("draw", {
ordered: true,
maxRetransmits: 2,
});
const metrics = pc.createDataChannel("metrics", {
ordered: false,
maxPacketLifeTime: 500,
});
W3C 规范 规定两者不能同时设置。选择其一即可。
5.1 ordered:false 的行为细节
当 ordered: false 时,SCTP 允许新消息「跳过」因丢包而卡住的重传队列:
这在游戏位置同步中是期望行为——过时的位置数据没有价值,与其等待重传不如处理最新状态。
六、binaryType 与消息类型
RTCDataChannel.send() 接受 string、Blob、ArrayBuffer 或 ArrayBufferView。接收端通过 binaryType 属性控制二进制消息的 JS 类型:
dc.send("Hello, world!");
const buffer = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
dc.send(buffer);
dc.binaryType = "arraybuffer";
dc.onmessage = (event) => {
if (typeof event.data === "string") {
console.log("Text:", event.data);
} else if (event.data instanceof ArrayBuffer) {
const view = new Uint8Array(event.data);
console.log("Binary:", view.length, "bytes");
}
};
| binaryType | 接收类型 | 适用 |
|---|---|---|
"blob"(默认) | Blob | 大文件、图片,可延迟读取 |
"arraybuffer" | ArrayBuffer | 需要立即解析二进制协议 |
SCTP 层通过 PPID 区分消息类型:
| PPID | 含义 |
|---|---|
| 51 | UTF-8 字符串 |
| 53 | 二进制(部分可靠) |
| 54 | 二进制(可靠) |
| 56 | 字符串(部分可靠) |
| 57 | 空消息 |
6.1 应用层协议设计建议
Data Channel 没有内置的消息分帧——send() 的每次调用对应一条 SCTP 消息。设计二进制协议时建议:
// 推荐:长度前缀帧格式
function encodeMessage(type, payload) {
const header = new ArrayBuffer(5);
const view = new DataView(header);
view.setUint8(0, type);
view.setUint32(1, payload.byteLength);
const combined = new Uint8Array(5 + payload.byteLength);
combined.set(new Uint8Array(header), 0);
combined.set(new Uint8Array(payload), 5);
return combined.buffer;
}
// type: 0x01=chat, 0x02=file-chunk, 0x03=control
文本消息可直接 JSON.stringify + send(),二进制数据用 ArrayBuffer。
七、背压控制:bufferedAmount
send() 是异步非阻塞的——调用后数据进入 SCTP 发送缓冲区,而非立即发出。如果发送速度超过网络吞吐,bufferedAmount 会持续增长,可能导致内存溢出。
7.1 背压控制模式
const CHUNK_SIZE = 16 * 1024;
const LOW_THRESHOLD = 256 * 1024;
dc.bufferedAmountLowThreshold = LOW_THRESHOLD;
dc.onbufferedamountlow = () => {
pumpSendQueue();
};
const sendQueue = [];
function enqueueSend(data) {
sendQueue.push(data);
pumpSendQueue();
}
function pumpSendQueue() {
while (sendQueue.length > 0 && dc.readyState === "open") {
if (dc.bufferedAmount > LOW_THRESHOLD) {
break;
}
const chunk = sendQueue.shift();
dc.send(chunk);
}
}
7.2 关键属性
| 属性/事件 | 含义 |
|---|---|
bufferedAmount | 当前缓冲区中未发送的字节数 |
bufferedAmountLowThreshold | 触发 bufferedamountlow 的阈值 |
bufferedamountlow | bufferedAmount 降到阈值以下时触发 |
7.3 消息大小限制
| 浏览器 | 最大消息大小 | 备注 |
|---|---|---|
| Chrome | 256 KB | SDP a=max-message-size:262144 |
| Firefox | 256 KB | 同 Chrome |
| Safari | 256 KB | 同 Chrome |
- 不检查 bufferedAmount 就循环 send → 内存暴涨,Tab 崩溃
- send 超过 256KB 的单条消息 → 抛出异常,大文件必须分片
- 在 readyState !== "open" 时 send → 抛出 InvalidStateError
- 忽略 onerror → 静默丢数据
- 背压阈值设太大 → 内存占用高;设太小 → 吞吐低
生产文件传输务必分片(16 KB 是经验值),并配合 bufferedAmount 控制发送速率。
八、文件传输完整示例
以下是一个带进度、背压、校验的生产级文件传输实现:
class P2PFileTransfer {
constructor(dataChannel) {
this.dc = dataChannel;
this.dc.binaryType = "arraybuffer";
this.dc.bufferedAmountLowThreshold = 256 * 1024;
this.CHUNK_SIZE = 16 * 1024;
this.sendQueue = [];
this.metadata = null;
this.receivedChunks = [];
this.receivedBytes = 0;
this.dc.onbufferedamountlow = () => this._pumpSend();
this.dc.onmessage = (e) => this._onMessage(e);
}
async sendFile(file) {
const meta = {
type: "file-start",
name: file.name,
size: file.size,
mime: file.type,
};
this.dc.send(JSON.stringify(meta));
const buffer = await file.arrayBuffer();
for (let offset = 0; offset < buffer.byteLength; offset += this.CHUNK_SIZE) {
this.sendQueue.push(buffer.slice(offset, offset + this.CHUNK_SIZE));
}
this._pumpSend();
}
_pumpSend() {
while (this.sendQueue.length > 0 && this.dc.readyState === "open") {
if (this.dc.bufferedAmount > this.dc.bufferedAmountLowThreshold) break;
this.dc.send(this.sendQueue.shift());
}
if (this.sendQueue.length === 0 && this.dc.bufferedAmount === 0) {
this.dc.send(JSON.stringify({ type: "file-end" }));
this.onSendComplete?.();
}
}
_onMessage(event) {
if (typeof event.data === "string") {
const msg = JSON.parse(event.data);
if (msg.type === "file-start") {
this.metadata = msg;
this.receivedChunks = [];
this.receivedBytes = 0;
this.onReceiveStart?.(msg);
} else if (msg.type === "file-end") {
this._assembleFile();
}
} else {
this.receivedChunks.push(event.data);
this.receivedBytes += event.data.byteLength;
this.onProgress?.(this.receivedBytes, this.metadata.size);
}
}
_assembleFile() {
const blob = new Blob(this.receivedChunks, { type: this.metadata.mime });
this.onReceiveComplete?.(blob, this.metadata);
}
}
8.1 传输时序
8.2 生产增强
| 增强 | 实现 |
|---|---|
| 校验 | 发送前计算 SHA-256,file-end 携带 hash,接收方验证 |
| 断点续传 | file-start 带 offset,接收方告知已收字节数 |
| 取消 | 发送 file-abort 控制消息,清空 sendQueue |
| 限速 | 动态调整 CHUNK_SIZE 或 LOW_THRESHOLD |
九、多 Data Channel 与 negotiated 模式
一个 PeerConnection 可创建多个 Data Channel,SCTP 在底层做流多路复用:
const chat = pc.createDataChannel("chat", { ordered: true, id: 0 });
const file = pc.createDataChannel("file", { ordered: true, id: 1 });
const control = pc.createDataChannel("control", {
ordered: false,
maxRetransmits: 0,
id: 2,
});
pc.ondatachannel = (event) => {
const { label } = event.channel;
switch (label) {
case "chat": setupChat(event.channel); break;
case "file": setupFileTransfer(event.channel); break;
case "control": setupControl(event.channel); break;
}
};
negotiated 模式:双方各自调用 createDataChannel 并指定相同 id:
const dc = pc.createDataChannel("sync", {
negotiated: true,
id: 0,
ordered: true,
});
| 模式 | 谁创建 | 适用 |
|---|---|---|
| 默认(negotiated: false) | 仅发起方 createDataChannel | 1v1 通话,Caller 决定开哪些通道 |
| negotiated: true | 双方都 createDataChannel | SFU/Mesh,双方对称创建 |
双方必须使用相同 id 且相同参数(ordered、maxRetransmits 等)。id 范围 0–65534,id 65535 保留给 SCTP 控制。重复 id 会导致关联失败。
十、Data Channel vs WebSocket
| 维度 | Data Channel | WebSocket |
|---|---|---|
| 路径 | P2P(或 TURN 中继) | 客户端 ↔ 服务器 |
| 延迟 | 最低(直连) | +1 跳服务器 RTT |
| 可靠性 | 可配置(有序/无序/部分可靠) | 始终可靠有序(TCP) |
| 服务端 | 不需要(信令除外) | 必须 |
| 连接数扩展 | 每对 Peer 一个 PC | 服务器连接数 = 用户数 |
| 防火墙 | 依赖 ICE/STUN/TURN | 标准 HTTPS 端口 |
| 消息大小 | ~256 KB/消息 | 无硬性限制 |
| 广播 | 不支持(需 Mesh/SFU) | 服务器可广播 |
| 持久化 | 无(P2P 断开即失) | 服务器可存储 |
最佳实践架构:
- 信令 → WebSocket(必须经服务器)
- 媒体 → SRTP(P2P 或 SFU)
- 大流量数据 → Data Channel(P2P 优先)
- 需要持久化/广播的消息 → WebSocket + 服务器
Slack/Discord 类应用:聊天消息走 WebSocket(持久化、搜索、离线推送),语音走 WebRTC SRTP,屏幕共享标注走 Data Channel(低延迟)。
十一、Data Channel 状态与生命周期
console.log(dc.readyState); // "connecting" | "open" | "closing" | "closed"
dc.close();
pc.close(); // 级联关闭所有 Data Channel
| 事件 | 触发时机 |
|---|---|
onopen | SCTP 关联建立 + DCEP 完成,可以 send |
onmessage | 收到对端消息 |
onclose | 通道关闭 |
onerror | 传输错误 |
onbufferedamountlow | 发送缓冲区降到阈值以下 |
Data Channel 不能在 closed 后重新 open——需要创建新的 Data Channel 或重建 PeerConnection。
十二、SDP 中的 Data Channel 协商
Data Channel 在 SDP 中通过 m=application 行描述:
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=ice-ufrag:...
a=ice-pwd:...
a=fingerprint:sha-256 ...
a=setup:actpass
a=mid:2
a=sctp-port:5000
a=max-message-size:262144
| 属性 | 含义 |
|---|---|
m=application | 非媒体 m-line,标识 Data Channel |
UDP/DTLS/SCTP | 协议栈 |
webrtc-datachannel | SCTP 载荷格式 |
a=sctp-port:5000 | SCTP 端口号(占位,实际由 DTLS 承载) |
a=max-message-size:262144 | 最大消息 256 KB |
发起方 createDataChannel 后再 createOffer,浏览器自动在 SDP 中加入上述字段。应答方 setRemoteDescription 时触发 ondatachannel。
十三、常见问题与排查
| 问题 | 原因 | 解决 |
|---|---|---|
| 对端收不到 ondatachannel | createDataChannel 在 createOffer 之后 | 调整调用顺序 |
| dc.readyState 一直 connecting | ICE/DTLS 未完成 | 检查 Ch5 ICE 状态 |
| send 抛 InvalidStateError | readyState 不是 open | 等 onopen 回调 |
| 大文件传输 Tab 崩溃 | 未做背压控制 | 检查 bufferedAmount |
| 消息乱序 | ordered: false | 预期行为;需要则改 true |
| 二进制收到 Blob 而非 ArrayBuffer | binaryType 默认 blob | 设 binaryType = "arraybuffer" |
| 双向都需要发数据 | 只有一方 createDataChannel | 用 negotiated 模式或双方都创建 |
| TURN 下传输慢 | 中继带宽限制 | 正常;对比 P2P 模式延迟 |
13.1 chrome://webrtc-internals 调试
- 打开
chrome://webrtc-internals - 找到 PeerConnection → dataChannels 部分
- 查看每个 channel 的
label、id、state、messagesSent/Received - 在 Stats 中搜索
sctp相关条目
const stats = await pc.getStats();
stats.forEach((report) => {
if (report.type === "data-channel") {
console.log("DC:", report.label, report.state,
"sent:", report.messagesSent,
"received:", report.messagesReceived,
"bytes:", report.bytesSent);
}
if (report.type === "transport") {
console.log("SCTP state:", report.sctpState);
}
});
十四、实战 Lab
Lab 1:最小 Data Channel 聊天
- 启动
examples/webrtc-lab/signaling/信令服务器 - 打开
client/ch06-data-channel/(或基于 ch02 添加 Data Channel,见 §4.4) - Caller 调用
createDataChannel("chat")后createOffer - 双方互发文本消息,确认
onmessage触发 - 在 webrtc-internals 中确认 Data Channel state = open
Lab 2:有序 vs 无序对比
- 创建两个 Channel:
ordered: true和ordered: false, maxRetransmits: 0 - 快速连续 send 100 条带序号的消息
- 在接收端对比:ordered 通道序号连续,unordered 可能乱序/丢包
- 用 Chrome DevTools → Network → Throttling 模拟 3G 丢包,效果更明显
Lab 3:文件传输 + 背压
- 选择 10 MB 以上文件
- 实现 §八 的
P2PFileTransfer类 - 对比「有背压」vs「无背压」的内存占用(Chrome Task Manager)
- 添加进度条 UI
Lab 4:binaryType 实验
- 分别设置
binaryType = "blob"和"arraybuffer" - 发送 PNG 图片二进制
- 对比接收端处理方式的差异(Blob 需
arrayBuffer()异步读取)
Lab 5:多 Data Channel
- 同时创建 chat(id=0)、file(id=1)、ping(id=2, unordered)
- 在 file 通道传大文件的同时,通过 chat 通道发消息——验证互不阻塞
- 在 webrtc-internals 中确认三个 data channel 条目
Lab 6:TURN 中继下的 Data Channel
- 部署 coturn(
examples/webrtc-lab/docker/coturn/) - 设置
iceTransportPolicy: "relay"强制 TURN - 确认 Data Channel 仍正常工作(数据经 TURN 中继)
- 用
performance.now()对比 P2P vs TURN 模式下 1MB 文件传输耗时
Lab 7:SCTP 统计与监控
setInterval(async () => {
const stats = await pc.getStats();
stats.forEach((r) => {
if (r.type === "data-channel") {
console.table({
label: r.label,
state: r.state,
messagesSent: r.messagesSent,
messagesReceived: r.messagesReceived,
bytesSent: r.bytesSent,
bytesReceived: r.bytesReceived,
});
}
});
}, 2000);
十五、本章小结
Phase 2(连接建立)到此完成:
| 章节 | 内容 | 状态 |
|---|---|---|
| Ch3 | 信令服务器 | ✅ |
| Ch4 | SDP 协商 | ✅ |
| Ch5 | ICE/STUN/TURN | ✅ |
| Ch6 | Data Channel | ✅ |
Phase 3 将深入媒体与安全内核,从 DTLS 握手与 SRTP 加密(Ch7) 开始。
系列导航
章节 主题 状态 0 架构全景与协议栈地图 ✅ 已发布 1 浏览器媒体 API 与设备管理 ✅ 已发布 2 第一个 P2P 视频通话 ✅ 已发布 3 信令服务器设计与会话状态机 ✅ 已发布 4 SDP 解剖与媒体协商 ✅ 已发布 5 ICE、STUN、TURN 与 NAT 穿透 ✅ 已发布 6 Data Channel 与 SCTP over DTLS ✅ 已发布 7 DTLS 握手与 SRTP 加密体系 ✅ 已发布 8 RTP/RTCP 媒体传输与 QoS ✅ 已发布 9 音视频编解码与 Simulcast 入门 ✅ 已发布 10 带宽估计与拥塞控制 GCC ✅ 已发布 11 Simulcast、SVC 与选择性订阅 ✅ 已发布 12 SFU/MCU/Mesh 架构与 Pion 实战 ✅ 已发布 13 调试工具链与可观测性 ✅ 已发布 14 TURN 集群部署与多区域扩展 ✅ 已发布 15 Capstone 生产级视频会议系统 ✅ 已发布
References
- RFC 8831 — WebRTC Data Channels
- RFC 8832 — SCTP-based Media Transport in the Datagram Transport Layer Security (DTLS) Protocol
- RFC 8834 — SCTP over DTLS for WebRTC
- RFC 4960 — SCTP Specification
- W3C — RTCDataChannel
- MDN — RTCDataChannel
- WebRTC for the Curious — Data Channel
- WebRTC for the Curious — 历史 / 多播 vs 单播
- HTML Spec — RTCDataChannelInit