宣布 tokio-io Crate

2017 年 3 月 17 日

今天我们很高兴地宣布一个新的 crate 和几个可在 Tokio 堆栈中使用的新工具。这代表了对各个部分进行多次并行更新的最终结果,它们恰好都在同一时间方便地落地!简而言之,改进包括

  • 一个新的 tokio-io crate 从 tokio-core 中提取出来,弃用了 tokio_core::io 模块。
  • bytes crate 引入 tokio-io,允许对缓冲进行抽象,并利用底层功能,如向量化 I/O。
  • Sink trait 中添加了一个新方法 close,以表达优雅关闭。

这些更改改进了 Tokio 的组织和抽象,以解决一些长期存在的问题,并应为未来的所有开发提供稳定的基础。同时,这些更改不是破坏性的,因为旧的 io 模块仍然以弃用形式提供。您可以立即通过 cargo update 开始使用所有这些 crate,并使用这些 crate 的最新 0.1.* 版本!

让我们更详细地深入了解每个更改,看看现在有哪些可用。

添加 tokio-io crate

现有的 tokio_core::io 模块提供了一些有用的抽象,但它们并非特定于 tokio-core 本身,tokio-io crate 的主要目的是提供这些核心实用程序,而无需运行时。通过 tokio-io,crate 可以依赖异步 I/O 语义,而无需将自己绑定到特定的运行时,例如 tokio-coretokio-io crate 旨在类似于 std::io 标准库模块,在为异步生态系统提供通用抽象方面。tokio-io 中提出的概念和 trait 是 Tokio 堆栈中完成的所有 I/O 的基础。

tokio-io 的主要内容是 AsyncReadAsyncWrite trait。这两个 trait 有点像 "拆分 Io trait",并且选择用于划分实现类似 Tokio 的读/写语义(非阻塞和通知 future 的任务)的类型。然后,这些 trait 与 bytes crate 集成,以提供一些方便的功能并保留旧功能,如 split

在一张白纸上,我们还借此机会将 tokio-core crate 中的 Codec trait 刷新为 EncoderDecoder trait,这些 trait 在 bytes crate 中的类型上运行(EasyBuf 不存在于 tokio-io 中,并且现在在 tokio-core 中已被弃用)。这些类型允许您从字节流快速移动到 SinkStream,准备好接受成帧消息。一个很好的例子是,通过 tokio-io,我们可以使用新的 length_delimited 模块与 tokio-serde-json 结合使用,以便立即启动并运行 JSON RPC 服务器,正如我们将在本文后面看到的那样。

总的来说,通过 tokio-io,我们还能够重新审视 API 设计中的一些小问题。这反过来又使我们能够 关闭针对 tokio-core 的一系列问题。我们认为 tokio-io 是 Tokio 堆栈向前迈进的一个重要补充。Crate 可以选择抽象于 tokio-io,而无需引入运行时,例如 tokio-core,如果他们愿意的话。

bytes 集成

tokio-core 的一个长期存在的缺点是其 EasyBuf 字节缓冲区类型。这种类型基本上正如其名称所示(一个“简单”的缓冲区),但不幸的是,通常不是您在高性能用例中所期望的。我们一直希望在这里有一个更好的抽象(和一个更好的具体实现)。

通过 tokio-io,您会发现 bytes crate 在 crates.io 上更加紧密地集成,并同时提供高性能和“简单”缓冲区所需的抽象。bytes crate 的主要内容是 BufBufMut trait。这两个 trait 提供了抽象任意字节缓冲区(可读和可写)的能力,并且现在与所有异步 I/O 对象上的 read_bufwrite_buf 集成。

除了抽象多种缓冲区类型的 trait 外,bytes crate 还附带了这些 trait 的两个高质量实现,即 BytesBytesMut 类型(分别实现 BufBufMut trait)。简而言之,这些类型代表引用计数缓冲区,这些缓冲区允许以高效的方式零拷贝提取数据切片。为了启动,它们还支持各种常见的操作,例如微型缓冲区(内联存储)、单所有者(可以在内部使用 Vec)、具有不相交视图的共享所有者(BytesMut)和具有可能重叠视图的共享所有者(Bytes)。

总的来说,我们希望 bytes crate 是您字节缓冲区抽象以及高质量实现的一站式商店,可让您快速运行。我们很高兴看到 bytes crate 的未来发展!

添加 Sink::close

我们最近落地的最终主要更改是在 Sink trait 上添加了一个新方法 close。到目前为止,在以通用方式实现“优雅关闭”方面一直没有很好的故事,因为没有清晰的方法向 sink 指示不再有项目将被推入其中。新的 close 方法正是为此目的而设计的。

close 方法允许通知 sink 不再有消息将被推入其中。然后,Sinks 可以借此机会刷新消息,并执行特定于协议的关闭。例如,此时的 TLS 连接将启动关闭操作,或者代理连接可能会发出 TCP 级别的关闭。通常,这将最终归结为新的 AsyncWrite::shutdown 方法。

添加 codec::length_delimited

随着 tokio-io 的发布,一个大型功能是添加了 length_delimited 模块(灵感来自 Netty 的 LengthFieldBasedFrameDecoder)。许多协议通过使用包含帧长度的帧头来分隔帧。作为一个简单的例子,以一个使用 u32 帧头来分隔帧有效负载的协议为例。线上的每个帧看起来像这样

+----------+--------------------------------+
| len: u32 |          frame payload         |
+----------+--------------------------------+

解析此协议可以很容易地通过 length_delimited::Framed 处理

// Bind a server socket
let socket = TcpStream::connect(
    &"127.0.0.1:17653".parse().unwrap(),
    &handle);

socket.and_then(|socket| {
    // Delimit frames using a length header
    let transport = length_delimited::FramedWrite::new(socket);
})

在上面的例子中,transport 将是缓冲区值的 Sink + Stream,其中每个缓冲区包含帧有效负载。这使得使用像 serde 这样的东西对帧进行编码和解码为值变得相当容易。例如,使用 tokio-serde-json,我们可以快速实现一个基于 JSON 的协议,其中每个帧都是长度分隔的,并且帧有效负载使用 JSON 编码

// Bind a server socket
let socket = TcpStream::connect(
    &"127.0.0.1:17653".parse().unwrap(),
    &handle);

socket.and_then(|socket| {
    // Delimit frames using a length header
    let transport = length_delimited::FramedWrite::new(socket);

    // Serialize frames with JSON
    let serialized = WriteJson::new(transport);

    // Send the value
    serialized.send(json!({
        "name": "John Doe",
        "age": 43,
        "phones": [
            "+44 1234567",
            "+44 2345678"
        ]
    }))
})

完整示例 在此

length_delimited 模块包含足够的配置设置来处理解析具有更复杂帧头的长度分隔帧,例如 HTTP/2.0 协议。

接下来是什么?

所有这些更改加在一起关闭了 futurestokio-core crate 中相当多的问题,我们认为 Tokio 的定位正符合我们对通用 I/O 和缓冲抽象的期望。与往常一样,我们很乐意在问题跟踪器上听到反馈,如果您发现问题,我们非常愿意合并 PR!否则,我们期待看到所有这些更改在实践中应用!

随着 tokio-coretokio-iotokio-servicetokio-proto 的基础不断巩固,Tokio 团队期待着适应和实施更雄心勃勃的协议,例如 HTTP/2。我们正在与 @seanmonstarHyper 密切合作,以开发这些基础的 HTTP 库。最后,我们希望在不久的将来扩展中间件的故事,使其与 HTTP 和通用 tokio-service 实现相关。更多相关信息即将发布!