宣布 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-core。tokio-io crate 旨在类似于 std::io
标准库模块,在为异步生态系统提供通用抽象方面。tokio-io 中提出的概念和 trait 是 Tokio 堆栈中完成的所有 I/O 的基础。
tokio-io 的主要内容是 AsyncRead
和 AsyncWrite
trait。这两个 trait 有点像 "拆分 Io
trait",并且选择用于划分实现类似 Tokio 的读/写语义(非阻塞和通知 future 的任务)的类型。然后,这些 trait 与 bytes crate 集成,以提供一些方便的功能并保留旧功能,如 split
。
在一张白纸上,我们还借此机会将 tokio-core crate 中的 Codec
trait 刷新为 Encoder
和 Decoder
trait,这些 trait 在 bytes crate 中的类型上运行(EasyBuf
不存在于 tokio-io 中,并且现在在 tokio-core 中已被弃用)。这些类型允许您从字节流快速移动到 Sink
和 Stream
,准备好接受成帧消息。一个很好的例子是,通过 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 的主要内容是 Buf
和 BufMut
trait。这两个 trait 提供了抽象任意字节缓冲区(可读和可写)的能力,并且现在与所有异步 I/O 对象上的 read_buf
和 write_buf
集成。
除了抽象多种缓冲区类型的 trait 外,bytes crate 还附带了这些 trait 的两个高质量实现,即 Bytes
和 BytesMut
类型(分别实现 Buf
和 BufMut
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 协议。
接下来是什么?
所有这些更改加在一起关闭了 futures 和 tokio-core crate 中相当多的问题,我们认为 Tokio 的定位正符合我们对通用 I/O 和缓冲抽象的期望。与往常一样,我们很乐意在问题跟踪器上听到反馈,如果您发现问题,我们非常愿意合并 PR!否则,我们期待看到所有这些更改在实践中应用!
随着 tokio-core、tokio-io、tokio-service 和 tokio-proto 的基础不断巩固,Tokio 团队期待着适应和实施更雄心勃勃的协议,例如 HTTP/2。我们正在与 @seanmonstar 和 Hyper 密切合作,以开发这些基础的 HTTP 库。最后,我们希望在不久的将来扩展中间件的故事,使其与 HTTP 和通用 tokio-service 实现相关。更多相关信息即将发布!