dcSCTP 从零实现 · sans-IO 的优雅

WebRTC 把 SCTP 从零重写成 dcSCTP,核心是「sans-IO」设计——协议栈不碰网络,只吃字节、吐字节和回调,纯逻辑。这一章读 dcsctp/socket/dcsctp_socket.h 的 DcSctpSocket:ReceivePacket/Send/Connect,以及 sans-IO 为什么让协议栈更易测、更安全。

作者 David更新于 第 78 篇(共 112 篇)

dcSCTP 从零实现 · sans-IO 的优雅

WebRTC 的 SCTP 是 Google 从零重写的 dcSCTP(net/dcsctp/)。为什么放着现成的库不用、要重造一个协议栈?答案藏在一个优雅的设计里——sans-IO。这一章讲它,顺带认识 dcSCTP 的核心 DcSctpSocket。

为什么重写

先说动机。WebRTC 原来用第三方的 usrsctp 库,但它问题不少:C 语言写的、历史包袱重、难测试(和网络 / 线程耦合)、有过安全漏洞、难维护。于是 Google 决定用现代 C++ 从零重写 dcSCTP,目标:更安全、更易测、更可控。而实现这些目标的关键手法,就是 sans-IO。

sans-IO:协议栈不碰网络

sans-IO(无 I/O)设计的核心:协议栈自己完全不做网络收发**,它只是一个“纯逻辑状态机”——你把收到的字节喂给它(ReceivePacket),它通过回调告诉你“该发这些字节出去了”“该起个定时器了”“来了条完整消息”;真正的网络收发、定时器,由外面的宿主代码做。** 看 DcSctpSocket(dcsctp/socket/dcsctp_socket.h)的接口:

// dcsctp/socket/dcsctp_socket.h(节选)
class DcSctpSocket : public DcSctpSocketInterface {
  void ReceivePacket(std::span<const uint8_t> data) override;  // 喂入收到的字节
  void Connect() override;                                     // 发起握手
  SendStatus Send(DcSctpMessage message, ...) override;        // 发一条消息
  // 输出全走 DcSctpSocketCallbacks:SendPacket / 起定时器 / OnMessageReceived...
};

注意 ReceivePacket 吃的是已经收到的字节(不是自己去 socket 读),DcSctpSocketCallbacks 里的 SendPacket 是让宿主去发(不是自己发)。dcSCTP 从不碰真正的 socket、线程、时钟——这些“副作用”全推给外面。它内部只有纯粹的 SCTP 协议逻辑:收到这个 chunk 该怎么处理、什么时候该 SACK、拥塞窗口怎么变。

sans-IO 的好处

这个“不碰 I/O”的设计,带来几个大好处:

把 I/O 剥离出去,协议栈就变成了一个确定性的、可完全单元测试的纯函数式状态机**——这是 sans-IO 最大的价值。**

  • 极易测试 —— 测试协议逻辑,不必开真的 socket、不必等真的网络。直接“喂一串字节 → 断言它回调了什么”。丢包、乱序、超时等场景,都能在测试里精确构造(喂特定字节、拨快模拟时钟),确定性复现。协议栈里最难测的并发 / 时序 bug,sans-IO 让它们变得可测。WebRTC 靠这个给 dcSCTP 建了海量单元测试。
  • 无线程麻烦 —— 它不自己起线程、不加锁(纯逻辑),线程模型由宿主决定,契合 WebRTC 的 TaskQueue。
  • 可移植 / 可复用 —— 不绑定任何网络 / OS API,理论上能用在任何环境(甚至模拟器、fuzzer)。dcSCTP 就有大量 fuzzer(dcsctp/fuzzers/)喂随机字节找 bug,正因为 sans-IO 让“喂字节”如此简单。

sans-IO 是近年协议栈设计的一股潮流(QUIC 的一些实现、Python 的 hyper 等都用)。它的哲学是:把“协议逻辑”和“I/O 副作用”彻底分离——逻辑纯粹好测,I/O 交给宿主。dcSCTP 是这个哲学在 WebRTC 里的漂亮实践,也是它比老 usrsctp 更安全可靠的根本原因。

DcSctpSocket 的世界

于是 dcSCTP 的运作是这样一幅图景:

图表加载中…

宿主(SctpTransportInternal)负责所有 I/O——从 DTLS 收到字节喂给 DcSctpSocket,DcSctpSocket 回调让它发字节 / 起定时器;DcSctpSocket 只管SCTP 协议逻辑。逻辑与 I/O 的这道干净的界,是 dcSCTP 优雅的核心。

小结与下一章

WebRTC 从零重写 SCTP 为 dcSCTP(弃老 usrsctp,求更安全 / 易测 / 可控),核心是 sans-IO 设计:DcSctpSocket 不碰网络 / 线程 / 时钟,只 ReceivePacket(吃字节)、Send(发消息)、Connect,输出全走回调(SendPacket 让宿主发、起定时器、OnMessageReceived)。宿主(SctpTransportInternal)做全部 I/O。好处:确定性、可完全单元测试(丢包乱序超时都能精确构造、海量 fuzzer)、无线程麻烦、可移植。逻辑与 I/O 分离,是它优雅与可靠的根本。

理解了 dcSCTP 的骨架,下一章看 SCTP 协议本身怎么开始——association 握手。👉 SCTP association 与握手。

参考资料

  • [1] libwebrtc 源码.net/dcsctp/socket/dcsctp_socket.h、net/dcsctp/public/dcsctp_socket.h(DcSctpSocket、DcSctpSocketCallbacks)。
  • [2] libwebrtc 源码.net/dcsctp/fuzzers/(sans-IO 使 fuzzing 简单)。
  • [3] sans-IO 设计模式 —— 协议逻辑与 I/O 分离。

本页目录