Table of contents
Open Table of contents
TL;DR
mio 是 Rust 生态中对操作系统 I/O 事件通知机制(epoll/kqueue/IOCP)的薄封装,由 Carl Lerche 创建,当前版本 1.2.1。它刻意只做一件事:事件就绪通知。没有 timer、没有 buffer、没有 async/await。tokio 和整个 Rust 异步生态建在 mio 之上。它与 C++ Asio 的核心差异:Asio 选 Proactor 是因为要向 IOCP 对齐;mio 选 Reactor 是因为 Rust 语言特性(ownership + async/await)让 Reactor 模式更自然,而 IOCP 的适配代价由 mio 在内部消化。
从问题出发:Rust 需要什么样的 I/O 层
2014 年,Rust 还没有稳定的 1.0。Carl Lerche(后来成为 tokio 的核心作者)面对的问题是:
- Rust 没有标准异步运行时:标准库只有阻塞 I/O(
std::net::TcpStream等) - 语言没有 async/await:2014-2018 年间,Rust 的异步全靠手写 Future trait
- 需要一个跨平台的事件通知层:能在 Linux/macOS/Windows 上统一提供 non-blocking I/O 事件
mio 诞生就是为了填这个空白——不是做一个完整的异步框架,而是做 最薄的、零开销的、跨平台的事件通知抽象。
mio 的设计哲学:Minimal Abstraction over OS Primitives
mio 的官方定义:
A fast, low-level I/O library for Rust focusing on non-blocking APIs and event notification for building high performance I/O apps with as little overhead as possible over the OS abstractions.
关键词是 as little overhead as possible。mio 的态度是:
- 不隐藏操作系统的语义:epoll 的边缘触发就暴露为边缘触发,不替你做电平触发的模拟
- 不做 buffer 管理:你的 buffer 你自己管,mio 不碰
- 不做 timer:定时器不是 I/O 事件通知,不属于 mio 的职责
- 不做 async/await 集成:Future/Waker 是上层(tokio)的事
- 不做连接管理:accept 了一个连接,怎么管理它的生命周期,是你的事
这和 Asio 的哲学形成鲜明对比:Asio 自称 toolkit,提供 timer、strand、buffer、协程支持;mio 只是一块 砖头——你用它砌墙,但它绝不帮你画图纸。
为什么 Rust 选 Reactor 而 Asio 选 Proactor
这是最核心的设计分歧,理解它才能理解两个生态的思路差异。
回顾:Reactor vs Proactor
| 维度 | Reactor (mio) | Proactor (Asio) |
|---|---|---|
| 通知内容 | 「fd 准备好了,你可以读了」 | 「读操作完成了,数据在 buffer 里」 |
| I/O 执行者 | 应用程序自己调 read()/write() | OS 内核完成 I/O |
| 缓冲区 | 事件到达后才需要 | 发起操作时就要提交 |
| 原生 OS 支持 | Linux epoll, macOS kqueue | Windows IOCP |
Asio 选 Proactor 的理由(已在 asio 文档中详述)
IOCP 是原生 Proactor,无法自然暴露为 Reactor API。Asio 向 Proactor 对齐,在 Linux/macOS 上用 Reactor 模拟 Proactor(收到就绪通知后内部立即执行 I/O,封装为完成事件)。
mio 选 Reactor 的理由
-
Rust 的 ownership 系统让 Reactor 更安全
Proactor 模式要求在发起异步操作时提交 buffer,操作系统在后台写入这个 buffer。在 C++ 中,这导致 buffer 的生命周期管理极其复杂——你必须保证 buffer 在 OS 完成写入之前不被释放。Asio 用 shared_ptr 和各种生命周期技巧来解决这个问题。
Reactor 模式没有这个问题:事件通知到达 → 你自己在当前上下文分配 buffer → 你自己调
read()→ buffer 的 ownership 完全在你手里。Rust 的 borrow checker 天然适配这个模型。 -
async/await+ Reactor = 自然的 Proactor 语义// tokio 在 mio Reactor 之上构建出的使用体验 let n = stream.read(&mut buf).await?; // 看起来像 Proactor(一步完成), // 但底层是 Reactor(先等就绪,再读取)Rust 的
async/await让编译器把 Reactor 的两步操作(等就绪 → 执行 I/O)编译成一个 state machine,用户写出来的代码看起来就像 Proactor。语言层面的抽象能力让底层 Reactor 的复杂性消失了。 -
IOCP 上模拟 Reactor 的代价可以内部消化
mio 在 Windows 上用 AFD(Auxiliary Function Driver)+ IOCP 来模拟 Reactor 语义。这确实有开销(后面详述),但这个开销被封装在 mio 内部,对上层透明。而且 Windows 的服务端高性能场景本身就少——大多数 Rust 异步服务跑在 Linux 上。
-
不需要跨操作 buffer 所有权转移
Proactor 需要「把 buffer 的控制权交给 OS,等 OS 用完了再还回来」。这在 Rust 的 ownership 模型中极其别扭——你要么用
unsafe,要么引入复杂的 lifetime 标注。Reactor 完全避免了这个问题。
对比总结
| 决策因素 | C++ Asio → Proactor | Rust mio → Reactor |
|---|---|---|
| OS 底线 | 必须高效支持 Windows IOCP | Linux 是主战场,Windows 可以妥协 |
| 语言能力 | 没有 async/await(直到 C++20),Proactor 语义更直接 | async/await 让 Reactor 也能写出 Proactor 体验 |
| 内存安全 | 手动管理 buffer 生命周期 | ownership 天然契合 Reactor |
| 设计定位 | 完整 toolkit | 最小化砖头,上层自己搭 |
mio 的版本演进
mio 0.6(2016.09)
- 核心 API 围绕
EventLoop和Handlertrait - 有内置的 timer 和 channel
- 支持
PollOpt::edge()/PollOpt::level()显式选择触发模式 Ready类型表示就绪状态(readable/writable/error/hup)Eventedtrait 是注册接口
mio 0.7(大规模重构)
- 删除
EventLoop,Poll成为唯一的事件循环原语 - 删除内置 timer 和 channel(职责推给上层)
- 引入
Registry分离注册操作和轮询操作 - 引入
Waker实现跨线程唤醒 Evented重命名为SourceReady被删除,改为Event上的is_readable()/is_writable()方法PollOpt被删除——不再允许用户选择边缘/电平触发,统一为边缘触发- 引入
Interest替代Ready用于注册 - 添加 Unix Domain Socket 支持(
UnixListener、UnixStream、UnixDatagram) - 最低 Rust 版本要求 1.39
mio 0.8(清理和稳定)
- 功能合并:
tcp+udp+uds→netfeature pipe+os-util→os-extfeature- 删除
TcpSocket(推荐用socket2crate) - 实现 I/O safety traits(
AsFd、From<OwnedFd>等) - 添加更多平台支持:QNX、visionOS、Haiku
mio 1.0 → 1.2.1(当前)
- Rust 2021 edition,最低版本 1.71(后升至 1.74)
- 删除 Solaris 支持
- 稳定 API,不再有重大 breaking change
- 三个作者:Carl Lerche、Thomas de Zeeuw、Tokio Contributors
- 属于
tokio-rs组织
演进的主题
每次大版本都在 做减法:删 timer → 删 channel → 删 PollOpt → 删 Ready → 删 TcpSocket。mio 的演进方向是越来越薄、越来越专注于纯粹的事件通知。这和 Asio 的演进方向相反——Asio 越来越厚(加 strand、加协程支持、加 cancellation slot)。
mio 在 Rust 异步生态中的位置
┌───────────────────────────────────────────┐
│ 应用层 (axum, actix-web, ...) │
├───────────────────────────────────────────┤
│ tokio / async-std │
│ (task scheduler, timer, async/await, │
│ buffered I/O, channel, sync primitives) │
├───────────────────────────────────────────┤
│ mio │
│ (Poll, Token, Interest, Events, Waker, │
│ TcpListener, TcpStream, UdpSocket) │
├───────────────────────────────────────────┤
│ OS Kernel │
│ (epoll / kqueue / IOCP) │
└───────────────────────────────────────────┘
mio 只负责中间那一层——把 OS 的事件通知 API 包成 Rust 类型安全的接口。上面所有的东西(定时器、任务调度、async/await 集成、buffered I/O)都不是 mio 的事。
与 C++ 开发者的对应关系
| C++ Asio 概念 | Rust 对应 | 备注 |
|---|---|---|
io_context | mio::Poll + tokio runtime | mio 只有 Poll,tokio 加上任务调度 |
async_read / async_write | mio 没有,tokio 的 AsyncRead / AsyncWrite | mio 层面只有 non-blocking read()/write() |
strand | 不需要 | Rust 的 ownership 保证了单线程内的安全 |
steady_timer | mio 没有,tokio 的 tokio::time::sleep | mio 刻意不做 timer |
ip::tcp::socket | mio::net::TcpStream | mio 提供但功能最小化 |
ip::tcp::acceptor | mio::net::TcpListener | 同上 |
| completion handler (callback) | Token(整数标识) | mio 不用回调,原因在下一篇详述 |