一、SCTP 是什么?为什么 WebRTC 选它?
聊数据通道,绕不开 SCTP。很多同学第一次接触 WebRTC 时,会问:为什么不用 TCP 或 UDP 直接传数据?
嗯,这个问题我当年也纠结过。TCP 可靠但太慢,UDP 快但不可靠。WebRTC 既要实时性,又要保证部分数据的可靠性——比如文件传输不能丢包,游戏指令可以丢但不能等太久。
SCTP(Stream Control Transmission Protocol)正好卡在中间。它是个传输层协议,兼具 TCP 的可靠性和 UDP 的消息边界特性。说白了,它是个「既要又要」的协议。
应用层 → SCTP → DTLS → UDP → IP
我个人习惯把 SCTP 比作「智能快递系统」。TCP 像挂号信——必须签收,顺序不能乱。UDP 像扔纸飞机——快,但丢了就丢了。SCTP 呢?它像顺丰——你可以选「必须本人签收」或「放快递柜就行」,还能同时发多个包裹,互不干扰。
二、SCTP 关联:建立连接的那点事
SCTP 里没有「连接」这个词,叫「关联」(Association)。为什么?因为 SCTP 支持多宿主(Multi-homing),一个关联可以绑定多个 IP 地址。这在 WebRTC 里其实用不上,但概念得懂。
2.1 关联建立的四次握手
SCTP 建立关联需要四次握手,比 TCP 多一次。我刚开始学的时候觉得多此一举,后来踩了坑才明白——这是为了防 SYN 攻击。
流程是这样的:
- 客户端发 INIT 块(携带验证标签)
- 服务端回 INIT-ACK(确认标签,分配资源)
- 客户端发 COOKIE-ECHO(携带 Cookie)
- 服务端回 COOKIE-ACK(关联建立)
注意第三步的 Cookie。SCTP 用 Cookie 机制防止资源被恶意耗尽。服务端收到 INIT 后不立刻分配资源,而是生成一个加密 Cookie 发回去。客户端必须原样返回这个 Cookie,服务端才真正分配内存。
2.2 验证标签(Verification Tag)
每个 SCTP 关联有两个验证标签——一个给发送方,一个给接收方。所有数据块都必须携带正确的标签,否则直接丢弃。
这玩意儿干嘛用的?防伪造。你想想看,如果攻击者伪造一个数据包塞进来,没有正确的标签,接收端直接扔掉。我在项目中见过有人误以为这是可选项,结果生产环境出了安全漏洞。
三、流(Stream):数据通道的「车道」
SCTP 的流是个好东西。一个关联里可以开多个流,每个流独立编号,互不干扰。
为什么需要流?举个例子:你在视频通话里同时传文件和发聊天消息。文件传输可以容忍延迟,但不能丢包。聊天消息可以丢一两条,但不能延迟。如果用同一个流,文件重传会阻塞聊天消息。
SCTP 的流解决了这个问题。每个流有自己的序号,消息在流内有序,但流之间完全独立。
| 特性 | TCP | SCTP 流 |
|---|---|---|
| 有序性 | 全局有序 | 流内有序,流间无序 |
| 阻塞 | 队头阻塞 | 流间无阻塞 |
| 数量 | 1 个 | 最多 65535 个 |
四、消息分片与重组:大消息怎么传?
SCTP 的传输单元叫「块」(Chunk)。一个 SCTP 包可以包含多个块。但问题来了:如果应用层发了一个 64KB 的消息,底层网络 MTU 只有 1500 字节,怎么办?
分片。
4.1 分片规则
SCTP 的分片发生在发送端。它会根据路径 MTU 把大消息切成多个 DATA 块。每个分片携带相同的流 ID 和流序号,但分片序号不同。
接收端收到后,按分片序号重组。如果某个分片丢了,接收端只重传那个分片,而不是整个消息。
嗯,这里要注意:SCTP 的分片对应用层完全透明。你发一个 100KB 的 ArrayBuffer,SCTP 自动帮你切好、传完、拼好。应用层拿到的就是完整消息。
4.2 无序传输
SCTP 支持无序传输。你可以在发送时设置无序标志,接收端收到后直接交给应用层,不用等前面的消息。
这有什么用?游戏里的位置更新。玩家位置每秒发 10 次,丢一两次无所谓,但必须立刻拿到最新的。用无序传输,每个位置包独立处理,不排队。
WebRTC 的数据通道默认是有序的。但你可以通过 ordered: false 选项创建无序通道。
// 创建无序数据通道
const channel = peerConnection.createDataChannel('game', {
ordered: false,
maxRetransmits: 0 // 不重传
});
五、SCTP 协议栈全景图
下面这张图展示了 SCTP 在 WebRTC 数据通道中的完整位置和内部结构。我画的时候特意把流、分片、关联的关系标清楚了。
六、实际开发中的注意事项
说了这么多理论,最后聊点实战经验。
6.1 流 ID 的管理
WebRTC 规范规定,数据通道的流 ID 从 0 开始递增。但如果你同时创建多个通道,ID 不一定连续。比如你创建了通道 0、1、2,然后关闭通道 1,再创建新通道,新通道的 ID 可能是 3,而不是 1。
我建议不要依赖流 ID 做业务逻辑。用数据通道的 label 属性来标识用途,更靠谱。
6.2 消息大小限制
SCTP 本身支持大消息,但浏览器实现有限制。Chrome 的 SCTP 实现默认最大消息是 256KB。超过这个值,发送会失败。
6.3 有序 vs 无序的选择
别盲目用无序。我见过有人把所有数据通道都设成无序,结果文件传输乱序了,还得应用层自己排序,得不偿失。
我的建议:
- 文件传输、二进制数据 → 有序 + 可靠
- 实时位置、游戏状态 → 无序 + 不可靠
- 聊天消息 → 有序 + 部分可靠(设置重传次数)
七、总结
SCTP 是数据通道的基石。理解了关联、流、分片这三个概念,你就能明白数据通道为什么既快又可靠。说白了,SCTP 把 TCP 的可靠性和 UDP 的灵活性结合在了一起,再加上流的概念,让 WebRTC 能同时处理多种类型的数据。
下次调试数据通道时,如果遇到消息丢失或顺序错乱,先别急着怀疑应用层代码。看看 SCTP 的流配置对不对,分片大小是否合适,验证标签有没有问题。这些底层细节,往往才是真正的坑。