数据通道的拥塞控制算法:RFC 4960 与 RFC 8260、用户可配置的参数
说到数据通道的拥塞控制,很多人第一反应是「这不就是 TCP 那套东西吗?」。嗯,还真不是。我刚开始接触 WebRTC 数据通道时也这么想,结果在项目中踩了个大坑——视频通话没问题,但文件传输一多,整个通道就卡死了。后来我才明白,数据通道的拥塞控制,其实是个更灵活、更「暴力」的东西。
为什么需要拥塞控制?
说白了,网络带宽是有限的。你发数据的速度如果超过了网络能承受的极限,数据包就会丢、延迟就会飙升。TCP 的做法是「慢启动、拥塞避免、快速重传」那一套,但 WebRTC 数据通道的场景不一样——它既要传实时数据(比如游戏操作),又要传大文件。你想想看,这两种数据对延迟和可靠性的要求完全不同。
所以,WebRTC 给了你选择权。你可以自己决定用哪种拥塞控制策略,甚至自己写一个。
RFC 4960:SCTP 的拥塞控制
数据通道底层用的是 SCTP(流控制传输协议)。RFC 4960 定义了 SCTP 的拥塞控制机制,它和 TCP 很像,但有几个关键区别。
| 特性 | TCP 拥塞控制 | SCTP(RFC 4960) |
|---|---|---|
| 慢启动 | 有 | 有 |
| 拥塞避免 | 有 | 有 |
| 快速重传 | 有 | 有 |
| 选择性确认(SACK) | 可选 | 强制 |
| 多流支持 | 无 | 有 |
注意看最后一行——多流支持。这是 SCTP 最大的优势。一个 SCTP 连接里可以跑多个流,每个流可以独立配置可靠性。我在项目中就遇到过这种情况:一个流传实时操作指令(要求低延迟),另一个流传日志文件(要求可靠)。如果用 TCP,你得开两个连接,但 SCTP 一个就够了。
核心要点:RFC 4960 的拥塞控制是「每个关联(association)级别」的,不是「每个流级别」的。也就是说,所有流共享同一个拥塞窗口。如果一个流丢包了,其他流也得跟着降速。
RFC 8260:SCTP 的改进
RFC 4960 是 2000 年的标准,到了 2017 年,RFC 8260 出来做了几个重要改进。我个人觉得最实用的就是「部分可靠性」和「消息交错」。
部分可靠性(Partial Reliability)
什么意思?就是你可以告诉 SCTP:「这个数据包,如果 100 毫秒内没发出去,就别发了。」或者「这个包最多重传 3 次,3 次还失败就放弃。」
这在实时场景下太有用了。比如游戏中的位置更新——你不需要保证每个包都到达,你只需要最新的位置信息。旧的数据到了反而会造成抖动。
我的经验:我曾经在一个实时协作编辑项目里用过部分可靠性。用户打字的内容用可靠模式传,但光标位置用部分可靠性传。效果很好,光标移动非常流畅,而且不会因为网络抖动导致卡顿。
消息交错(Message Interleaving)
这个改进解决了一个很实际的问题。假设你有一个大文件在传(比如 10MB),同时有一个小消息要发(比如「你好」)。在 RFC 4960 下,小消息得等大文件传完才能发。你想想看,这得多坑?
RFC 8260 允许你把小消息「插队」到大文件前面。实现方式是在 SCTP 头部加了一个字段,标明消息的优先级。
// 伪代码示例:配置消息交错
const channel = peerConnection.createDataChannel('chat', {
ordered: false, // 允许乱序
maxRetransmits: 0, // 不重传
priority: 'high' // 高优先级(RFC 8260 扩展)
});
用户可配置的参数
WebRTC 的数据通道 API 给了你几个关键参数来控制拥塞行为。我列一下最常用的:
- ordered:是否保证消息顺序。设为 false 可以避免队头阻塞。
- maxPacketLifeTime:消息的最大存活时间(毫秒)。超时未发送则丢弃。
- maxRetransmits:最大重传次数。超过次数则放弃。
- protocol:子协议标识,可以用来区分不同的数据流类型。
注意:maxPacketLifeTime 和 maxRetransmits 不能同时设置。这是 SCTP 协议的限制,你只能选一个。我见过有人两个都设了,结果浏览器直接报错。
实际项目中的配置建议
根据我的经验,不同场景的配置差异很大。这里给几个参考:
| 场景 | ordered | maxRetransmits | maxPacketLifeTime | 说明 |
|---|---|---|---|---|
| 实时游戏操作 | false | 0 | 不设 | 不重传,丢了就丢了 |
| 文件传输 | true | 不设 | 不设 | 必须可靠有序 |
| 视频字幕 | false | 不设 | 500ms | 超过半秒的字幕没意义 |
| 聊天消息 | true | 5 | 不设 | 重传5次,保证到达 |
拥塞控制的核心逻辑
为了让你更直观地理解,我画了一张图。这张图展示了数据通道拥塞控制的核心流程:
这张图的核心逻辑是:应用层把数据交给 SCTP 层,SCTP 根据你配置的参数(可靠或部分可靠)决定如何发送。然后所有流共享一个拥塞窗口,由拥塞控制算法统一管理。说白了,不管你怎么配置,最终都得听拥塞控制的。
如何选择?
我个人的建议是:
- 如果你不确定,先用默认配置(ordered=true, 不设 maxRetransmits 和 maxPacketLifeTime)。这是最稳妥的。
- 如果你做实时应用,一定要用部分可靠性。否则网络一抖,整个体验就崩了。
- 如果你做文件传输,考虑用多个流。一个流传文件,另一个流传控制信号。这样文件传输的拥塞不会影响控制信号。
避坑指南:我曾经在一个项目中把所有数据都放在一个流里,结果文件传输导致控制信号延迟飙升。后来拆成两个流,问题就解决了。记住:多流是 SCTP 给你的超能力,别浪费。
好了,关于拥塞控制算法和可配置参数,就聊这么多。下一节我们会深入代码层面,看看如何在浏览器中实际配置这些参数。