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

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

- 作者：David（道雾轩）
- 专栏：音视频与 WebRTC 详解（https://daiw.org/manual/webrtc.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/webrtc/dcsctp-intro
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

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

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

## 为什么重写

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

## sans-IO：协议栈不碰网络

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

  ```cpp
  // 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 该怎么处理](https://daiw.org/manual/webrtc/sctp-chunks-packets)、[什么时候该 SACK](https://daiw.org/manual/webrtc/sctp-reliability)、[拥塞窗口怎么变](https://daiw.org/manual/webrtc/sctp-flow-congestion)。
</Callout>

## sans-IO 的好处

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

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

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

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

## DcSctpSocket 的世界

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

```mermaid
flowchart LR
    subgraph HOST["宿主(WebRTC:SctpTransport)"]
        NET["网络收到字节"] -->|"ReceivePacket(bytes)"| SOCK
        SOCK -->|"回调 SendPacket(bytes)"| SEND["宿主去发字节"]
        SOCK -->|"回调 起定时器"| TIMER["宿主的 TaskQueue 定时"]
        SOCK -->|"回调 OnMessageReceived"| MSG["交给数据通道"]
    end
    SOCK["DcSctpSocket(纯逻辑状态机)"]
```

宿主（[`SctpTransportInternal`](https://daiw.org/manual/webrtc/data-channel-overview)）负责所有 I/O——从 [DTLS](https://daiw.org/manual/webrtc/dtls-handshake) 收到字节喂给 `DcSctpSocket`，`DcSctpSocket` 回调让它发字节 / 起定时器；`DcSctpSocket` 只管[SCTP 协议逻辑](https://daiw.org/manual/webrtc/sctp-association)。**逻辑与 I/O 的这道干净的界，是 dcSCTP 优雅的核心。**

## 小结与下一章

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

理解了 dcSCTP 的骨架，下一章看 SCTP 协议本身怎么开始——**association 握手**。👉 [SCTP association 与握手](https://daiw.org/manual/webrtc/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 分离。
