Showing preview only (210K chars total). Download the full file or copy to clipboard to get everything.
Repository: dslchd/tokio-cn-doc
Branch: master
Commit: 59b321022304
Files: 45
Total size: 146.1 KB
Directory structure:
gitextract_jl6cffu2/
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── doc/
│ ├── AsyncInDepth.md
│ ├── BridgingWithSyncCode.md
│ ├── Channels.md
│ ├── Framing.md
│ ├── Glossary.md
│ ├── GracefulShutdown.md
│ ├── HelloTokio.md
│ ├── IO.md
│ ├── Introduction.md
│ ├── Select.md
│ ├── SharedState.md
│ ├── Spawning.md
│ ├── Streams.md
│ └── Topics.md
└── src/
├── bin/
│ ├── channels-demo.rs
│ ├── common/
│ │ ├── delay.rs
│ │ └── mod.rs
│ ├── echo-client.rs
│ ├── echo-server-copy.rs
│ ├── echo-server.rs
│ ├── future-impl-demo.rs
│ ├── hello-redis-server.rs
│ ├── hello-tokio.rs
│ ├── io-demo.rs
│ ├── mini-tokio-one.rs
│ ├── mini-tokio-two.rs
│ ├── mini-tokio.rs
│ ├── my-future.rs
│ ├── not-compile.rs
│ ├── select-borrow.rs
│ ├── select-demo.rs
│ ├── select-syntax-demo.rs
│ ├── send-bound.rs
│ ├── shared-state.rs
│ └── store-value.rs
├── main.rs
├── relational/
│ ├── connection.rs
│ ├── frame_enum.rs
│ └── mod.rs
└── tools/
├── mod.rs
└── tools.rs
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/target
.idea
Cargo.lock
*.iml
================================================
FILE: Cargo.toml
================================================
[package]
name = "tokio-cn-doc"
version = "0.1.0"
authors = ["dengshenglong <dslchd@@qq.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = {version = "0.2", features = ["full"]}
mini-redis = "0.2"
bytes = "1.0.1"
rand = "0.5.5"
crossbeam = "0.7"
futures = "0.3"
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 dslchd
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Tokio 中文文档
## 1.说明
Tokio 它是Rust语言的一种异步**运行时** 可以用来编写可靠,异步的Rust应用. 它有以下几个特点:
* 快速: Tokio是零成本抽象的,可以带给你接近裸机的性能.
* 可靠的: Tokio基于Rust语言的生命周期,类型系统,并发模型来减少bug和确保线程安全.
* 可扩展: Tokio有非常小的占用,并能处理背压(backpressure)和取消(cancellation)操作.
Tokio是一个事件驱动的非阻塞I/O平台,用于使用Rust编写异步应用. 在较高的层次上,它提供了几个主要的组件:
* 基于多线程与工作流窃取的 任务调度器 [scheduler](https://docs.rs/tokio/latest/tokio/runtime/index.html).
* 响应式的,基于操作系统的事件队列(比如,epoll, kqueue, IOCP, 等...).
* 异步的[TCP and UDP](https://docs.rs/tokio/latest/tokio/net/index.html) socket.
这些组件提供了用来构建异步应用所需要的运行时组件.
[官方原文指南](https://tokio.rs/tokio/tutorial).
[bin](src/bin) 目录下有一些可以参考的,基于官方文档的示例代码.
我的其它翻译:[Actix-web 3.0 中文文档](https://github.com/dslchd/actix-web3-CN-doc).
## 2.中文文档索引
### 指南
#### [介绍(Introduction)](doc/Introduction.md)
#### [你好 Tokio (Hello Tokio)](doc/HelloTokio.md)
#### [Spawning](doc/Spawning.md)
#### [共享状态(Shared state)](doc/SharedState.md)
#### [通道(Channels)](doc/Channels.md)
#### [I/O](doc/IO.md)
#### [帧(Framing)](doc/Framing.md)
#### [深入异步(Async in depth)](doc/AsyncInDepth.md)
#### [Select](doc/Select.md)
#### [流(Streams)](doc/Streams.md)
### [主题(Topics)](doc/Topics.md)
#### [使用同步代码桥接(Bridging with sync code)](doc/BridgingWithSyncCode.md)
#### [优雅关机(Graceful Shutdown)](doc/GracefulShutdown.md)
### [词汇表(Glossary)](doc/Glossary.md)
### [API文档(API documentation)](https://docs.rs/tokio)
## 3.其它
Tokio是一个非常值得学习的,Rust生态中的网络库. 有些类似 "Rust界的Netty" 的感觉,很多上层库,或包,或框架都是基于它(比如 Actix-web).
因此作为一名 _Rustaceans_ 学习与使用,或理解Tokio意义重大. 此**中文文档**是本人在学习Tokio后的 **"果实"** . 我顺便整理了出来.
由于水平有限,不敢妄言翻译的有多好,其中难免会有错误和遗漏,如果发现烦请一并指正. 望此文档能给同样对Tokio感兴趣的人的学习带来一点帮助.
================================================
FILE: doc/AsyncInDepth.md
================================================
## 深入异步(Async in depth)
到现在,我们完成了异步Rust和Tokio相当全面的介绍. 现在我们将更深入的研究Rust异步运行时模型. 在本教程最开始,我们暗示过Rust的异步采用了一种独特的方式. 现在我们将解释其含义.
## Futures
作为回顾,让我们写一个非常基础的异步函数. 与到目前为止的教程相比,这并不是什么新东西.
```rust
use tokio::net::TcpStream;
async fn my_async_fn() {
println!("hello from async");
let _socket = TcpStream::connect("127.0.0.1:3000").await.unwrap();
println!("async TCP operation complete");
}
```
我们调用这个函数并得到一些返回值. 我们调用`.await`得到这个值.
```rust
#[tokio::main]
async fn main() {
let what_is_this = my_async_fn();
// 上面调用后,这里并没有任何打印内容
what_is_this.await;
// 文本被打印了,且socket链接建立和关闭
}
```
`my_async_fn()` 返回的是一个future值. 此Future它是一个实现了标准库中 [std::future::Future](https://doc.rust-lang.org/std/future/trait.Future.html) trait 的值. 它们是包含正在进行异步计算的值.
[std::future::Future](https://doc.rust-lang.org/std/future/trait.Future.html) trait定义如下:
```rust
use std::pin::Pin;
use std::task::{Context, Poll};
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx:&mut Context) -> Poll<Self::Output>;
}
```
[associated type](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#specifying-placeholder-types-in-trait-definitions-with-associated-types) `Output` 是一个future完成后产生的类型.
[Pin](https://doc.rust-lang.org/std/pin/index.html) 类型是Rust在 `async` 函数中如何支持借用. 查看 [standard library](https://doc.rust-lang.org/std/pin/index.html) 了解更多的细节.
与其它语言实现的future不一样, 一个Rust的future不代表在后台发生的计算,而是Rust的future就是计算本身. future的所有者通过轮询future来推进计算. 这是通过调用 `Future::poll`来完成的.
### 实现 `Future` (Implementing `Future`)
让我们来实现一个非常简单的future. 它有以下几个特点:
1. 等待到一个特定的时间点.
2. 输出一些文本到STDOUT.
3. 产生一个String.
```rust
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
struct Delay {
when: Instant,
}
impl Future for Delay {
type Output = &'static str;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>)
-> Poll<&'static str>
{
if Instant::now() >= self.when {
println!("Hello world");
Poll::Ready("done")
} else {
// 现在忽略这一行
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
#[tokio::main]
async fn main() {
let when = Instant::now() + Duration::from_millis(10);
let future = Delay { when };
let out = future.await;
assert_eq!(out, "done");
}
```
### Async fn as a Future
在main函数中,我们实例化一个future并在它上面调用 `.await`. 在异步函数中,我们可以在任何实现了 `Future` 的值上调用 `.await` .
反过来说, 调用一个 `async` 函数会返回一个实现了 `Future` 的匿名类型. 在 `async fn main()` 中,生成的future大致为:
```rust
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
enum MainFuture {
// 初始化时,从未轮询过
State0,
// 等待 `延迟` , 比如. `future.await` 这一行.
State1(Delay),
// future已经完成.
Terminated,
}
impl Future for MainFuture {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>)
-> Poll<()>
{
use MainFuture::*;
loop {
match *self {
State0 => {
let when = Instant::now() +
Duration::from_millis(10);
let future = Delay { when };
*self = State1(future);
}
State1(ref mut my_future) => {
match Pin::new(my_future).poll(cx) {
Poll::Ready(out) => {
assert_eq!(out, "done");
*self = Terminated;
return Poll::Ready(());
}
Poll::Pending => {
return Poll::Pending;
}
}
}
Terminated => {
panic!("future polled after completion")
}
}
}
}
}
```
Rust的Future是一种**状态机**. 这里 `MainFuture` 代表future可能的状态枚举. future开始于`State0` 状态. 当调用`poll`时, future会尝试尽可能的推进其内部的状态.如果future能够完成,则返回包含异步计算输出的`Poll::Ready`.
如果future**不能够**完成, 通常是由于资源不够而等待,这个时候返回`Poll::Pending`. 接收到`Poll::Pending`会向调用者表明future会在将来某个时刻完成,并且调用者应该稍候再次调用`poll`函数.
我们还看到future由其它future组合. 在外部future上调用`poll`会导致在内部future上调用`poll`函数.
## 执行器(Executors)
异步Rust函数返回future. 必须在Future上调用`poll`来推进其状态. Future可以被其它Future组合. 因此,问题来了,调用最外部的future的`poll`意味着什么?
回想一下,要运行异步函数,必须将它们传递给`tokio::spawn`或者使用`#[tokio::main]`注解main函数. 这样的结果是生成的外部future提交给Tokio的执行器.执行器负责在外部Future上调用`Future::poll`,来驱动异步计算的完成.
### Mini Tokio
为了更好的理解这一切是如何融合的,让我们实现自己的迷你版本的Tokio!完整的代码在 [这里](https://github.com/tokio-rs/website/blob/master/tutorial-code/mini-tokio/src/main.rs) .
```rust
use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
use futures::task;
fn main() {
let mut mini_tokio = MiniTokio::new();
mini_tokio.spawn(async {
let when = Instant::now() + Duration::from_millis(10);
let future = Delay { when };
let out = future.await;
assert_eq!(out, "done");
});
mini_tokio.run();
}
struct MiniTokio {
tasks: VecDeque<Task>,
}
type Task = Pin<Box<dyn Future<Output = ()> + Send>>;
impl MiniTokio {
fn new() -> MiniTokio {
MiniTokio {
tasks: VecDeque::new(),
}
}
/// 在 mini-tokio 实例之上产生一个future
fn spawn<F>(&mut self, future: F)
where
F: Future<Output = ()> + Send + 'static,
{
self.tasks.push_back(Box::pin(future));
}
fn run(&mut self) {
let waker = task::noop_waker();
let mut cx = Context::from_waker(&waker);
while let Some(mut task) = self.tasks.pop_front() {
if task.as_mut().poll(&mut cx).is_pending() {
self.tasks.push_back(task);
}
}
}
}
```
这将运行异步块. 使用请求延迟来创建一个`Delay`实例并等待它. 然而,我们的实现到目前为止有一个重大的缺陷. 我们的执行器绝不会休眠. 执行器不断
循环所有产生的future并对其进行轮询. 大多时候,future不准备执行更多的工作,并会返回`Poll::pending`. 这一过程会消耗CPU并且通常没有效率.
理想的情况下,我们仅仅想让mini-tokio在future在有进展的时候去轮询future. 当阻塞任务的资源准备好执行请求操作的时候,就会发生这种情况. 如果
任务想从TCP socket中读取数据,那么我们只想在TCP socket接收到数据时轮询任务. 在我们的方案中,任务在到达指定的`Instant`时被阻塞. 理想情况下
mini-tokio只会在该时间过去后再轮询任务.
为了达到这一目的,在对一个资源进行轮询且资源未准备好时,资源转换为就绪状态后将发送一个通知.
## 唤醒(Wakers)
通过该系统(译者注: 指唤醒),资源在通知等待它的任务时表明已经准备就绪,可以继续进行一些其它的操作了.
让我们再次看看`Future::poll`的定义:
```rust
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output>;
```
`poll`函数中的`Context`参数有一个`waker()`方法. 此方法返回一个绑定到当前任务的[Waker](https://doc.rust-lang.org/std/task/struct.Waker.html) .
`Waker`中又有一个`wake()`方法. 调用这个方法会向执行器发出信息,说明应该安排相关任务的执行计划. 当资源的状态转换到就绪状态时,它们会调用`wake()`方法,来通知执行者轮询任务来推进资源的状态.
### 更新`Delay`(Updating `Delay`)
我们能更新`Delay`来使用唤醒(wakers):
```rust
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
use std::thread;
struct Delay {
when: Instant,
}
impl Future for Delay {
type Output = &'static str;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>)
-> Poll<&'static str>
{
if Instant::now() >= self.when {
println!("Hello world");
Poll::Ready("done")
} else {
// 在当前任务上获取一个waker句柄
let waker = cx.waker().clone();
let when = self.when;
// 生产一个定时器线程
thread::spawn(move || {
let now = Instant::now();
if now < when {
thread::sleep(when - now);
}
waker.wake();
});
Poll::Pending
}
}
}
```
现在,一旦请求的持续时间过去后,调用任务就会得到通知,执行器可以确保再次安排任务. 下一步就是更新mini-tokio来监听唤醒通知.
我们的`Delay`实现仍然有一些其它的问题. 我们将在后面修复它.
```text
当一个future返回 Poll::Pending时,它必须确保waker能在某个时刻发出信息. 忘记此操作的结果就是任务会无限的挂起.
返回 Poll::Pending后忘记唤醒任务是常见bug的来源.
```
回忆一下`Delay`的第一个迭代版本. 下面是future的实现:
```rust
impl Future for Delay {
type Output = &'static str;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
if Instant::now() >= self.when {
println!("Hello world");
Poll::Ready("done")
}else {
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
```
在返回`Poll::Pending`之前,我们调用了`cx.waker().wake_by_ref()`. 这是为了满足future的组合操作. 通过返回`Poll::Pending`,我们负责
发信号给waker. 因为我们没有实现计时器(timer)线程,所以我们向唤醒程序(waker)发送了内联信息. 这样做的结果是为了future能重新被调度,再次执行,
并且可能还没有完全准备好.
请注意,你可以向waker发送更多的不是必须的信号. 在这种特殊的情况下,即使我们没有准备好继续操作,我们也会向waker发出信号. 除了浪费一个CPU时钟周期外,并没有什么问题. 但是,这种特殊的实现将导致非常繁忙的循环.
### 更新Mini-Tokio(Updating Mini-Tokio)
接下来就是更新Mini Tokio 来接收waker的通知. 我们想让执行器在仅当任务被唤醒时才运行它们,为了做到这一点,Mini Tokio将提供自己的waker.
当waker被调用时,与它相关的任务会被排队执行. Mini Tokio在轮询future时将waker传递给future.
更新后的Mini Tokio将使用channel来存储计划任务. 通道(Channel)允许任务以队列的方式从任意线程执行. Wakers必须是 `Send`和`Sync` 类型的,
因此我们使用 crossbeam 包中的channel,因为标准库中的channel不是`Sync`的.
`Send` 与 `Sync` 是Rust提供的与并发相关的一种标记trait. Send 类型可以在不同的线程中传递. 大多数类型都是 Send ,但是像 [Rc](https://doc.rust-lang.org/std/rc/struct.Rc.html) 却不是. 可以通过不可变引用并发访问的类型是 Sync. 一个类型是 Send 但不是 Sync ---- 一个很好的例子是 [Cell](https://doc.rust-lang.org/std/cell/struct.Cell.html),可以通过不可变引用对其修改,因此它不能并发的共享访问.
更多关于 `Send` 与 `Sync` 的细节可以参考[chapter in the Rust book](https://doc.rust-lang.org/book/ch16-04-extensible-concurrency-sync-and-send.html).
在 `Cargo.toml` 中添加如下依赖:
```toml
crossbeam = "0.7"
```
然后,更新`MiniTokio` 结构体:
```rust
use crossbeam::channel;
use std::sync::Arc;
struct MiniTokio {
scheduled: channel::Receiver<Arc<Task>>,
sender: channel::Sender<Arc<Task>>,
}
struct Task {
// 这一块后面再填写
}
```
Wakers是`Sync`类型的并且它可以被克隆(Clone). 当调用`wake`时,必须安排任务来执行. 为了实现这个,我们使用channel. 当在waker上调用`wake()`时,
任务被推送到channel的发送方. 我们的`Task`结构体将实现wake的逻辑. 为了做到这一点,它需要包含生成的Future和channel发送方.
```rust
use std::sync::{Arc, Mutex};
struct Task {
// Mutex能使用任务实现'同步(sync)'效果,在任意时刻仅能有一个线程能够访问future.
// Mutex (在此场景下)不需要非常正确,真实的tokio没有在这里使用Mutex,但是真实的tokio
// 使用了更多行的代码来实现这一点.
future: Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>,
executor: channel::Sender<Arc<Task>>,
}
impl Task {
fn schedule(self: &Arc<Self>) {
self.executor.send(self.clone());
}
}
```
为了安排任务,`Arc`将会被clone,并将它通过channel发送. 现在,我们需要将`schedule`函数与 [std::task::Waker](https://doc.rust-lang.org/std/task/struct.Waker.html) 挂钩.
标准库提供了一套低级别的API [manual vtable construction](https://doc.rust-lang.org/std/task/struct.RawWakerVTable.html) 来做这个. 这种策略为实现者提供了最大的灵活性,
但是需要大量的unsafe(不安全)的样板代码. 取而代之的是,我们可以直接使用[RawWakerVTable](https://doc.rust-lang.org/std/task/struct.RawWakerVTable.html),
我们使用[futures](https://docs.rs/futures/)包提供的[ArcWake](https://docs.rs/futures/0.3/futures/task/trait.ArcWake.html) 工具.
这可以使我们能实现一个简单的trait,来将我们的`Task`结构暴露为一个waker.
在`Cargo.toml`中添加如下依赖来拉取`futures`.
```toml
futures = "0.3"
```
然后实现[futures::task::ArcWake](https://docs.rs/futures/0.3/futures/task/trait.ArcWake.html) .
```rust
use futures::task::ArcWake;
use std::sync::Arc;
impl ArcWake for Task {
fn wake_by_ref(arc_self: &Arc<Self>) {
arc_self.schedule();
}
}
```
当上面的计时器(timer)线程调用`waker.wake()`时,任务被发送到channel通道中去. 下一步我们在`MiniTokio::run()`函数中实现接收和执行任务的功能.
```rust
impl MiniTokio {
fn run(&self) {
while let Ok(task) = self.scheduled.recv() {
task.poll();
}
}
/// 初始化一个新的 mini-tokio 实例.
fn new() -> MiniTokio {
let (sender, scheduled) = channel::unbounded();
MiniTokio { scheduled, sender }
}
/// 在 mini-tokio 实例上产生一个 future
///
/// 给future包装task并推其推送到 `scheduled` 队列中.
fn spawn<F>(&self, future: F)
where
F: Future<Output = ()> + Send + 'static,
{
Task::spawn(future, &self.sender);
}
}
impl Task {
fn poll(self: Arc<Self>) {
// 从task实例上创建一个waker. 它使用 ArcWake
let waker = task::waker(self.clone());
let mut cx = Context::from_waker(&waker);
// 没有其它线程试图锁住future
let mut future = self.future.try_lock().unwrap();
// 轮询future
let _ = future.as_mut().poll(&mut cx);
}
// 使用指定的future产生一个新的task.
//
// 初始化一个新的包含了指定future的task,并将其它推送给 sender. channel另外一半的receiver将接收到它并执行.
fn spawn<F>(future: F, sender: &channel::Sender<Arc<Task>>)
where
F: Future<Output = ()> + Send + 'static,
{
let task = Arc::new(Task {
future: Mutex::new(Box::pin(future)),
executor: sender.clone(),
});
let _ = sender.send(task);
}
}
```
这里发生了多件事. 首先,实现了`MiniTokio::run()`. 这个函数循环运行,从通道中接收计划任务. 当任务被唤醒时,任务被推送到channel中,这些
任务在执行时能够取得进展(译者注: 指poll后任务本身的状态能得到推进).
另外,`MiniTokio::new()`与`MiniTokio::spawn()`函数也使用channel来调整了一下,而不是使用`VecDeque`. 当一个新的任务产生时,为它们分配
channel 发送部分的副本,任务可以在运行时使用该副本来调度本身.
`Task::poll()` 函数使用来自`futures`包中的`ArcWake`工具创建一个waker. 此waker用来创建一个`task::Context`. `task::Context`传递给`poll`.
## 概要(Summary)
我们现在已经看到了异步Rust的端到端原理示例. Rust的`async/await` 特性背后由trait支持. 这就允许使用第三方包,像tokio来提供执行细节.
* Rust的异步操作是惰性的,需要调用者对其进行轮询.
* Wakers被传递给future,以将future与调用它的任务联系起来.
* 当一个资源没有准备好完成时,`Poll::Pending`被返回并记录任务的唤醒程序(waker).
* 当一个资源变为就绪状态时,就会通知任务的唤醒程序(waker).
* 执行器接收到通知并安排任务来执行.
* 任务再一次被轮询,这一次资源是就绪状态并且任务能够取得进展.
## 一些零碎的结论(A few loose ends)
回顾一下,当我们实现`Delay`时,我们说过还要更多的问题要修复. Rust的异步模型允许单个future在执行时跨任务移动. 考虑一下如下代码:
```rust
use futures::future::poll_fn;
use std::future::Future;
use std::pin::Pin;
#[tokio::main]
async fn main() {
let when = Instant::now() + Duration::from_millis(10);
let mut delay = Some(Delay { when });
poll_fn(move |cx| {
let mut delay = delay.take().unwrap();
let res = Pin::new(&mut delay).poll(cx);
assert!(res.is_pending());
tokio::spawn(async move {
delay.await;
});
Poll::Ready(())
}).await;
}
```
`poll_fn` 函数使用闭包来创建一个`Future`实例. 上面的代码片段创建了一个`Delay`实例,并将其轮询一次,然后将`Delay`实例发送给一个新的任务,
再等待它. 在这个示例中,使用不同的`Waker`实例多次调用`Delay::poll`. 我们早期的实现中无法处理这种情况,并且由于通知了错误的任务,因此产生的
任务会永远处于休眠状态.
为了修复我们早期的实现,我们可以像下面这样做:
```rust
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Waker};
use std::thread;
use std::time::{Duration, Instant};
struct Delay {
when: Instant,
waker: Option<Arc<Mutex<Waker>>>,
}
impl Future for Delay {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
// 首时,如果第一次future被调用,产生一个计时器线程
// 如果计时器线程已经在运行了,要确保存储的 waker能匹配上当前任务的 waker
if let Some(waker) = &self.waker {
let mut waker = waker.lock().unwrap();
// 检查存储的waker能否匹配上当前任务的waker
// 这是必须的,因为 Delay future实例可能在调用poll时移动在不同的task中去
// 如果发生了这种情况, 给定的“Context”中包含的waker将有所不同,我们必须要更新存储的waker以反映此更改.
if !waker.will_wake(cx.waker()) {
*waker = cx.waker().clone();
}
} else {
let when = self.when;
let waker = Arc::new(Mutex::new(cx.waker().clone()));
self.waker = Some(waker.clone());
// 这是第一次 poll 被调用时产生一个计时器线程
thread::spawn(move || {
let now = Instant::now();
if now < when {
thread::sleep(when - now);
}
// 持续时间过去后,通过激活waker来通知调用者.
let waker = waker.lock().unwrap();
waker.wake_by_ref();
});
}
// 一旦waker被存储且计时器已经开始,就是检查delay是否完成的时候了.
// 通过检查当前时刻来完成.
//
// 如果持续时间过了后, future已经完成 Poll::Ready就会返回
if Instant::now() >= self.when {
Poll::Ready(())
} else {
// 持续时间没有过去,future没有完成就返回 PollPending
//
// Future trait 要求当返回 Pending 时,future将确保一旦再次对future进行轮询,就会发出指定唤醒信息.
//
// 在我们的例子中,通过这里返回的 Pending 我们可以保证一旦请求的持续时间过去后,我们将调用包含在 Context 参数中的指定waker
// 我们通过产生一个计时器线程来确保这一点.
//
// 如果我们忘记激活waker,任务将会无限的持起.
Poll::Pending
}
}
}
```
它涉及到一点,但是这个想法是,在每次轮询时,future都会检查所提供的waker是否与先前记录的waker相匹配. 如果两个waker匹配,则什么也不发生.
如果它们不匹配,则原来记录的waker必须被更新.
### `Notify` **utility**
我们演示了如何使用waker手动实现`Delay` future. Wakers是异步Rust能工作的基础. 通常,不需要降低到该级别. 比如说,在`Delay`的案例中,
我们可以使用[tokio::sync::Notify](https://docs.rs/tokio/0.3/tokio/sync/struct.Notify.html) 工具完全使用`async/await`来实现它.
这个实用工具提供了基础的任务通知机制. 它处理了waker的一些细节,包括确保记录的waker与当前任务的waker匹配.
使用[Notify](https://docs.rs/tokio/0.3/tokio/sync/struct.Notify.html),我们可以像下面这样,使用 `async/await` 实现`Delay`功能:
```rust
use tokio::sync::Notify;
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::thread;
async fn delay(dur: Duration) {
let when = Instant::now() + dur;
let notify = Arc::new(Notify::new());
let notify2 = notify.clone();
thread::spawn(move || {
let now = Instant::now();
if now < when {
thread::sleep(when - now);
}
notify2.notify_one();
});
notify.notified().await;
}
```
← [Framing](Framing.md)
→ [Select](Select.md)
================================================
FILE: doc/BridgingWithSyncCode.md
================================================
## 使用同步代码桥接(Bridging with sync code)
在大多数使用 `Tokio` 的示例中,我们使用 `#[tokio::main]` 来标记 `main` 函数,并使得整个工程是异步的。
然而并不是所有项目都需要这样。比方说,GUI类的应用可能希望在main线程上运行GUI代码,在另外一个线程上运行`tokio`
的运行时。
这一页将告诉你如何使用 `async/await` 来隔离项目中的一小部分。
### `#[tokio::main]` 指什么? (Waht `#[tokio::main]` expands to)
`#[tokio::main]` 是一个宏,是一个用来替代调用非异步代码 `main` 函数的宏,并启动一个运行时。(有点绕,自行组织了下好理解点)。比如像下面这样:
```rust
#[tokio::main]
async fn main() {
println!("hello world!");
}
```
它可以转换成如下写法:
```rust
fn main() {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
println!("Hello world");
})
}
```
通过宏,为了在我们自己的项目中使用 `async/await` , 我们可以做一些类似的事,利用 `block_on` 方法在适当的地方进入异步上下文。
### mini-redis的同步接口(A synchronous interface to mini-redis)
在这一章节中,我们将介绍如何通过存储 `Runtime` 对象并使用它的 `block_on` 方法来构建与 `mini-redis` 同步的接口。在下面的章节中我们将讨论
一些替代方法和如何使用这些替代方法。
我们要包装的接口是一个异步的 [Client](https://docs.rs/mini-redis/0.4/mini_redis/client/struct.Client.html) 类型。它有几个方法,我们将
实现这几个方法的阻塞版本:
* [Client::get](https://docs.rs/mini-redis/0.4/mini_redis/client/struct.Client.html#method.get)
* [Client::set](https://docs.rs/mini-redis/0.4/mini_redis/client/struct.Client.html#method.set)
* [Client::set_expires](https://docs.rs/mini-redis/0.4/mini_redis/client/struct.Client.html#method.set_expires)
* [Client::publish](https://docs.rs/mini-redis/0.4/mini_redis/client/struct.Client.html#method.publish)
* [Client::subscribe](https://docs.rs/mini-redis/0.4/mini_redis/client/struct.Client.html#method.subscribe)
为了做到这一点,我们引入一个 `src/blocking_client.rs` 文件,并使用异步 `Client` 类型的包装结构对其进行初始化。
```rust
use tokio::net::ToSocketAddrs;
use tokio::runtime::Runtime;
pub use crate::client::Message;
/// 与Redis server 建立链接
pub struct BlockingClient {
/// The asynchronous `Client`.
inner: crate::client::Client,
/// A `current_thread` runtime for executing operations on the
/// asynchronous client in a blocking manner.
/// 一个 `current_thread` 运行时用来在异步 client 上执行阻塞操作
rt: Runtime,
}
pub fn connect<T: ToSocketAddrs>(addr: T) -> crate::Result<BlockingClient> {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
// Call the asynchronous connect method using the runtime.
// 使用rt(实际上是tokio的异步运行时)来进行异步connect
let inner = rt.block_on(crate::client::connect(addr))?;
Ok(BlockingClient { inner, rt })
}
```
这里,我们在构造函数中展示了如何在非异步上下文中执行异步方法的示例。我们在 `tokio` 的异步运行时 [Runtime](https://docs.rs/tokio/1/tokio/runtime/struct.Runtime.html)
类型上使用 [block_on](https://docs.rs/tokio/1/tokio/runtime/struct.Runtime.html#method.block_on) 方法,它执行一个异步方法并返回结果。
有一个很重要的细节是这里使用了 [current_thread](https://docs.rs/tokio/1/tokio/runtime/struct.Builder.html#method.new_current_thread) 运行时。
通常,当我们使用 Tokio 时默认情况下使用使用 [multi_thread](https://docs.rs/tokio/1/tokio/runtime/struct.Builder.html#method.new_multi_thread) 运行时,
它会产生一堆的后台线程因此它可以在同一时刻非常高效的运行很多东西。在我们的示例中,我们在同一时候仅仅只做一件事,因此我们没必要在后台运行多个线程。这使得
[current_thread](https://docs.rs/tokio/1/tokio/runtime/struct.Builder.html#method.new_current_thread) 非常适合,而不是产生多个线程。
调用 [enable_all](https://docs.rs/tokio/1/tokio/runtime/struct.Builder.html#method.enable_all) 在tokio的运行时上来启动IO和计时驱动。
如果它没被启动,运行时将不能执行IO或计时器功能。
```text
因为 `current_thread` 运行时不产生线程,它仅仅在 `block_on` 被调用时运行。一旦 `block_on` 返回,所有在运行时上产生的任务都会被冻结,直到你
再次调用 `block_on` 方法。 如果想要在不调用 `block_on` 时产生的任务也要保持运行,那么请使用 `multi_thread` 运行时。
```
一旦我们有了这样的结构,大部分方法实现起来就很容易了:
```rust
use bytes::Bytes;
use std::time::Duration;
impl BlockingClient {
pub fn get(&mut self, key: &str) -> crate::Result<Option<Bytes>> {
self.rt.block_on(self.inner.get(key))
}
pub fn set(&mut self, key: &str, value: Bytes) -> crate::Result<()> {
self.rt.block_on(self.inner.set(key, value))
}
pub fn set_expires(
&mut self,
key: &str,
value: Bytes,
expiration: Duration,
) -> crate::Result<()> {
self.rt.block_on(self.inner.set_expires(key, value, expiration))
}
pub fn publish(&mut self, channel: &str, message: Bytes) -> crate::Result<u64> {
self.rt.block_on(self.inner.publish(channel, message))
}
}
```
[Client::subscribe](https://docs.rs/mini-redis/0.4/mini_redis/client/struct.Client.html#method.subscribe) 方法更加有趣,
因为它将 `Client` 对象转换成 `Subscriber` 对象。 我们可以像下面这样实现它:
```rust
/// 一个能进入的 发布/订阅模式的客户端
///
/// Once clients subscribe to a channel, they may only perform
/// 一旦有客户端订阅一个通道,它们仅能执行 pub/sub 相关的命令。
/// pub/sub related commands. The `BlockingClient` type is
/// `BlockingClient` 类型被转换成一个 `BlockingSubscriber` 类型是为了防止非 pub/sub 方法被调用。
/// transitioned to a `BlockingSubscriber` type in order to
/// prevent non-pub/sub methods from being called.
pub struct BlockingSubscriber {
/// The asynchronous `Subscriber`.
/// 异步 `Subscriber`
inner: crate::client::Subscriber,
/// A `current_thread` runtime for executing operations on the
/// asynchronous client in a blocking manner.
/// `current_thread` 运行时用于以阻塞的方式来运行异步client.
rt: Runtime,
}
impl BlockingClient {
pub fn subscribe(self, channels: Vec<String>) -> crate::Result<BlockingSubscriber> {
let subscriber = self.rt.block_on(self.inner.subscribe(channels))?;
Ok(BlockingSubscriber {
inner: subscriber,
rt: self.rt,
})
}
}
impl BlockingSubscriber {
pub fn get_subscribed(&self) -> &[String] {
self.inner.get_subscribed()
}
pub fn next_message(&mut self) -> crate::Result<Option<Message>> {
self.rt.block_on(self.inner.next_message())
}
pub fn subscribe(&mut self, channels: &[String]) -> crate::Result<()> {
self.rt.block_on(self.inner.subscribe(channels))
}
pub fn unsubscribe(&mut self, channels: &[String]) -> crate::Result<()> {
self.rt.block_on(self.inner.unsubscribe(channels))
}
}
```
因此,`subscribe` 方法会首先使用运行时将异步的 `Client` 转换成异步的 `Subscriber` 。 然后它将生成的 `Subscriber`与 `Runtime` 一起存储
,并使用 [block_on](https://docs.rs/tokio/1/tokio/runtime/struct.Runtime.html#method.block_on) 来实现各种方法。
注意到,异步的 `Subscriber` 结构体有一个非异步的方法 `get_subscribed` 。为了处理这个,我们直接使用非运行时的方式来调用它。
### 其它方法(Other approaches)
上面的章节解释了实现同步包装器的简单方式,但这不是唯一的方法。一般的方法还有:
* 创建一个 [Runtime](https://docs.rs/tokio/1/tokio/runtime/struct.Runtime.html) 并在异步代码上调用 [block_on](https://docs.rs/tokio/1/tokio/runtime/struct.Runtime.html#method.block_on)
* 创建一个 [Runtime](https://docs.rs/tokio/1/tokio/runtime/struct.Runtime.html) 并在它上面 [Spawn](https://docs.rs/tokio/1/tokio/runtime/struct.Runtime.html#method.spawn) 一些事。
* 在一个分隔的线程上运行一个 `Runtime` 并给它发消息。
我们已经看到了第一种的实现方式,另外两种将在下面来介绍。
#### 在一个Runtime上产生一些东西 (Spawning things on a runtime)
[Runtime](https://docs.rs/tokio/1/tokio/runtime/struct.Runtime.html) 对象上有一个 [spawn](https://docs.rs/tokio/1/tokio/runtime/struct.Runtime.html#method.spawn) 方法。
当我们调用这个方法时,你可以在运行时上产生一个新任务。比如像下面这样:
```rust
use tokio::runtime::Builder;
use tokio::time::{sleep, Duration};
fn main() {
let runtime = Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.unwrap();
let mut handles = Vec::with_capacity(10);
for i in 0..10 {
handles.push(runtime.spawn(my_bg_task(i)));
}
// Do something time-consuming while the background tasks execute.
// 当后台任务执行时做一些事来消耗下时间
std::thread::sleep(Duration::from_millis(750));
println!("Finished time-consuming task.");
// Wait for all of them to complete.
// 等待所有任务完成
for handle in handles {
// The `spawn` method returns a `JoinHandle`. A `JoinHandle` is
// a future, so we can wait for it using `block_on`.
// spawn 方法返回一个 JoinHandle. 它是一个 future, 因此我们可以在它上面使用 block_on
runtime.block_on(handle).unwrap();
}
}
async fn my_bg_task(i: u64) {
// By subtracting, the tasks with larger values of i sleep for a
// shorter duration.
// 通过相减,较大值的任务 sleep 时间更短
let millis = 1000 - 50 * i;
println!("Task {} sleeping for {} ms.", i, millis);
sleep(Duration::from_millis(millis)).await;
println!("Task {} stopping.", i);
}
```
```text
Task 0 sleeping for 1000 ms.
Task 1 sleeping for 950 ms.
Task 2 sleeping for 900 ms.
Task 3 sleeping for 850 ms.
Task 4 sleeping for 800 ms.
Task 5 sleeping for 750 ms.
Task 6 sleeping for 700 ms.
Task 7 sleeping for 650 ms.
Task 8 sleeping for 600 ms.
Task 9 sleeping for 550 ms.
Task 9 stopping.
Task 8 stopping.
Task 7 stopping.
Task 6 stopping.
Finished time-consuming task.
Task 5 stopping.
Task 4 stopping.
Task 3 stopping.
Task 2 stopping.
Task 1 stopping.
Task 0 stopping.
```
在上面的示例中,我们在运行时上产生了10个后台任务,并等待所有任务完成。比如,这可能是在图形应用程序中实现后台联网的好方法,因为网络请求太耗时间,因而
无法在main gui 线程上运行它们。相反,你可以在后台运行 tokio 运行时来生成网络请求,并在请求完成将任务信息发送回GUI线程代码,如果你想要进度条(效果)
,甚至可以增量发送。
在这个例子中,运行时配置 [multi_thread](https://docs.rs/tokio/1/tokio/runtime/struct.Builder.html#method.new_multi_thread) 是很重要的。
如果你将它改为 `current_thread` 运行时,你会发现耗时的任务会在任何后台任务开始之前完成。这是因为在 `current_thread` 上产生的后台任务,只会在调用
`block_on` 期间运行,否则运行时没有任何地方可以运行它们。
例子,通过调用 [spawn](https://docs.rs/tokio/1/tokio/runtime/struct.Runtime.html#method.spawn) 返回的 [JoinHandle](https://docs.rs/tokio/1/tokio/task/struct.JoinHandle.html)
对象上的 `block_on` 方法来等待生成任务的完成,但这也并非唯一方法。这里还有一些其它的替代方案:
* 使用消传递通道,比如: [tokio::sync::mpsc](https://docs.rs/tokio/1/tokio/sync/mpsc/index.html) 。
* 修改一个受保护的值,比如 `Mutex` 对于GUI中的进度条来说,这会是一个很好的方法,其中GUI的每一帧读取共享值。
`spawn` 方法也可用于 [Handle](https://docs.rs/tokio/1/tokio/runtime/struct.Handle.html) 类型。可以clone `handle` 类型来获得运行时的多个句柄,
每一个 `handle` 可被用于在运行时上产生新的任务。
#### 发送消息(Sending messages)
第三种技术是生成一个运行时(Runtime)并使用消息传递与其通信。它是一种最灵活的方式,你可以在下面找到一个基本的使用示例:
```rust
se tokio::runtime::Builder;
use tokio::sync::mpsc;
pub struct Task {
name: String,
// info that describes the task
}
async fn handle_task(task: Task) {
println!("Got task {}", task.name);
}
#[derive(Clone)]
pub struct TaskSpawner {
spawn: mpsc::Sender<Task>,
}
impl TaskSpawner {
pub fn new() -> TaskSpawner {
// Set up a channel for communicating.
// 设置一个用于沟通的 channel
let (send, mut recv) = mpsc::channel(16);
// Build the runtime for the new thread.
//
// The runtime is created before spawning the thread
// to more cleanly forward errors if the `unwrap()`
// panics.
// 为新线程构造一个 运行时(runtime)
// 运行时在 线程之前创建出来可以更清楚的来传递错误,如果使用了 unwrap() panics 的话。
let rt = Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
std::thread::spawn(move || {
rt.block_on(async move {
while let Some(task) = recv.recv().await {
tokio::spawn(handle_task(task));
}
// Once all senders have gone out of scope,
// the `.recv()` call returns None and it will
// exit from the while loop and shut down the
// thread.
// 一旦所有的 sender 超出作用域时,`.recv()` 的调用会返回None, 它将退出 while 循环并关闭线程
});
});
TaskSpawner {
spawn: send,
}
}
pub fn spawn_task(&self, task: Task) {
match self.spawn.blocking_send(task) {
Ok(()) => {},
Err(_) => panic!("The shared runtime has shut down."),
}
}
}
```
这个示例可以通过多种方式来配置。比如,你可以使用 [Semaphore](https://docs.rs/tokio/1/tokio/sync/struct.Semaphore.html) 信号量来限制活动的任务数量,
或者你可以使用相反方向的channel向 spawner 发送响应。当你以这种方式生成运行时时, 它是一个 [actor](https://ryhl.io/blog/actors-with-tokio/) 类型。
← [主题](Topics.md)
→ [优雅关机](GracefulShutdown.md)
================================================
FILE: doc/Channels.md
================================================
## 通道(Channels)
现在我们已经学习了一些与Tokio相关的并发知识, 让我们将这些知识应用到客户端. 假设我们想运行两个并发的Redis命令. 我们可以为每一个命令产生
一个任务来处理. 然后这两个命令将同时发生.
首写我们可能尝试写像下面这样的代码:
```rust
use mini_redis::client;
#[tokio::main]
async fn main() {
// 建立一个与Server的链接
let mut client = client::connect("127.0.0.1:6379").await.unwrap();
// 产生两个任务, 一个获取key, 另外一个设置key值.
let t1 = tokio::spawn(async {
let res = client.get("hello").await;
});
let t2 = tokio::spawn(async {
client.set("foo", "bar".into()).await;
});
t1.await.unwrap();
t2.await.unwrap();
}
```
上面的代码不能被编译, 因为两个任务都需要以某种方式访问 `client` . 而 `client` 没有实现 `Copy` , 因此如果没有一些可以促进"共享"的代码, 它将
无法编译. 另外, `Client::set` 需要 `&mut self`, 这意味着需要独占的访问权才能调用它. 我们可以为每一个任务打开一个链接,但这不是一个好办法.
我们不能使用 `std::sync::Mutex` , 因为 `.await` 需要在持有锁的情况下调用. 我们可以使用 `tokio::sync::Mutex` , 但是这样又仅允许一个
进行中的请求. 如果客户端实现 [pipelining](https://redis.io/topics/pipelining) , 异步互斥锁又不能充分的利用链接了.
## 消息传递(Message passing)
结论就是使用消息传递机制. 该模式涉及产生一个专门的任务来管理 `client` 中的资源. 任何希望发出请求的任务都会向`client` 的任务发送一条消息.
`client` 任务代表发送方发出请求, 并将响应返回给发送方.
使用这种策略,可以建立单个的链接. 管理 `client` 的任务可以获取独占访问权, 以便来调用 `get` 和 `set` . 另外, 通道还用作缓冲区.
客户端任务比较繁忙的时候,可能会将操作发送到客户端任务. 一旦 `client` 任务可以用来处理新链接, 它将从通道中拉取下一个请求(进行处理).
这样的方式可以提高吞吐量,并可以扩展的方式来支持链接池.
## Tokio的通道原语(Tokio's channel primitives)
Tokio提供了许多通道( [number of channels](https://docs.rs/tokio/0.2/tokio/sync/index.html) ), 每一种都有其对应的用途.
* [mpsc](https://docs.rs/tokio/0.2/tokio/sync/mpsc/index.html) : 多生产者(multi-producer)单消费者(single-consumer)通道. 可以发送许多的值.
* [oneshot](https://docs.rs/tokio/0.2/tokio/sync/oneshot/index.html) : 单生产者(single-producer)单消费者(single-consumer)通道. 可以发送单个值.
* [broadcast](https://docs.rs/tokio/0.2/tokio/sync/broadcast/index.html) : 多生产者多消费者(广播). 可以发送许多值,每一个接收者都能看到每一个值.
* [watch](https://docs.rs/tokio/0.2/tokio/sync/watch/index.html) : 单生产者多消费者. 可以发送许多值,但是不会保留历史记录. 接收者仅能看到最新的值.
如果你需要一个多生产者多消费者通道且仅仅只想让一个消费者看到所有消息, 你可以使用 [async-channel](https://docs.rs/async-channel/) 包.
在异步Rust之外还有其它通道可以使用,比如, [std::sync::mpsc](https://doc.rust-lang.org/stable/std/sync/mpsc/index.html) 和 [crossbeam::channel](https://docs.rs/crossbeam/latest/crossbeam/channel/index.html) . 这些通道通过阻塞线程来等待消息, 这在
异步代码中是不允许的.
在本章节中我们将使用 [mpsc](https://docs.rs/tokio/0.2/tokio/sync/mpsc/index.html) 与 [oneshot](https://docs.rs/tokio/0.2/tokio/sync/oneshot/index.html) .
后面的章节将讨论其它的消息通道类型. 本章完整的代码可以在 [这里](https://github.com/tokio-rs/website/blob/master/tutorial-code/channels/src/main.rs) 找到.
## 定义消息类型(Define the message type)
在大多数情况下, 使用消息传递时, 接收消息的任务会响应多个命令. 在我们的案例中, 任务将响应 `GET` 与 `SET` 命令. 为了对这个建模,我们首先
定义一个 `Command` 的枚举, 并为每种命令类型包含一个变体.
```rust
use bytes::Bytes;
#[derive(Debug)]
enum Command {
Get {
key: String,
},
Set {
key: String,
val: Bytes,
}
}
```
## 创建通道(Create the channel)
在 `main` 中 创建 `mpsc` 通道.
```rust
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
// 创建一个最大容量为32的通道
let (mut tx, mut rx) = mpsc::channel(32);
// ... 这里先休息一下
}
```
`mpsc` 通道被用来发送一个命令到管理Redis链接的任务中. 多生产者的能力是能让许多的任务发送消息. 创建的通道返回两个值, 一个是发送者(Sender)一个是
接收者(receiver). 它们两者被分开使用. 他们可能移动到不同的任务中去.
被创建的通道容量为32. 如果消息的发送速度大于接收的速度, 通道会储存它们. 一旦通道中存了32条消息时,就会调用 `send(...).await` 进入睡眠状态,
直到接收者删除一条消息为止.(译者注: 就是说当接收者有能力能再次处理消息时, 睡眠状态才会结束).
通过 **克隆** (**cloning**) `Sender` 可以完成多个任务的发送. 比如像下面这样:
```rust
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
let (tx, mut rx) = mpsc::channel(32);
let tx2 = tx.clone();
tokio::spawn(async move {
tx.send("sending from first handle").await;
});
tokio::spawn(async move {
tx2.send("sending from second handle").await;
});
while let Some(message) = rx.recv().await {
println!("GOT = {}", message);
}
}
```
两条消息都发送到单个 `Receiver` 处理. 不可能克隆 `mpsc` 通道中的接收者.
当每个 `Sender` 超出作用域范围或者被dropped时, 它不能再发送更多的消息到通道中. 此时, `Receiver` 上的 `rev` 调用都将返回 `None` ,
这意味着所有发送者都已经消失且通道已关闭.
在我们管理Redis链接的任务中,它知道一旦通道关闭就能关闭Redis链接, 因为该链接不再使用了.
## 产生管理任务(Spawn manager task)
下一步, 产生一个任务来处理来自通道的消息. 首先, 建立与Redis的链接. 然后, 通过Redis链接发出接收到的命令.
```rust
use mini_redis::client;
// move 关键字用来移动 rx 所有权到task中去
let manager = tokio::spawn(async move {
// 建立与Server的链接
let mut client = client::connect("127.0.0.1:6379").await.unwrap();
// 开始接收消息
while let Some(cmd) = rx.recv().await {
use Command::*;
match cmd {
Get { key } => {
client.get(&key).await;
}
Set { key, val } => {
client.set(&key, val).await;
}
}
}
});
```
现在,更新这两个任务来使用通道发送命令,而不是直接在Redis的链接上发出命令.
```rust
// Sender 被移动到task中了, 这里有两个任务, 所以我们需要第二个 Sender
let tx2 = tx.clone();
// 产生两个任务一个得到key值,一个设置key的值
let t1 = tokio::spawn(async move {
let cmd = Command::Get {
key: "hello".to_string(),
};
tx.send(cmd).await.unwrap();
});
let t2 = tokio::spawn(async move {
let cmd = Command::Set {
key: "foo".to_string(),
val: "bar".into(),
};
tx2.send(cmd).await.unwrap();
});
```
## 接收响应
最后一步就是接收来管理任务的响应. `GET` 命令需要获取值, 而 `SET` 命令需要知道操作是否完成.
为了传递响应,可以使用 `oneshot` 通道. `oneshot` 通道是一个经过了优化的单生产者单消费者通道,用来发送单个值. 在我们的案例中,单个值就是响应.
与 `mpsc` 类似, `oneshot` 返回一个发送者(Sender)和一个接收者(receiver)处理器.
```rust
use tokio::sync::oneshot;
let (tx,rx) = oneshot::channel();
```
与 `mpsc` 不同, `oneshot` 它不能指定任何容量, 因为容量始终为1. 另外, 两个处理器都不能被克隆(译者注: 指 tx, rx).
为了接收到来自管理任务的响应, 在发送一个命令之前, 一个 `oneshot` 通道将被创建. 通道 `Sender` 的一半包含在管理任务的命令中. 接收方的一半用来接收响应.
首先, 更新 `Command` 来包含一个 `Sender` . 为了方便, 为 `Sender` 定义一个类型别名.
```rust
use tokio::sync::oneshot;
use bytes::Bytes;
/// 多个不同的命令在单个通道上复用.
#[derive(Debug)]
enum Command {
Get {
key: String,
resp: Responder<Option<Bytes>>,
},
Set {
key: String,
val: Vec<u8>,
resp: Responder<()>,
},
}
/// 由请求者提供并通过管理任务来发送,再将命令的响应返回给请求者.
type Responder<T> = oneshot::Sender<mini_redis::Result<T>>;
```
现在,更新发出命令的任务来包括 `oneshot::Sender` .
```rust
let t1 = tokio::spawn(async move {
let (resp_tx, resp_rx) = oneshot::channel();
let cmd = Command::Get {
key: "hello".to_string(),
resp: resp_tx,
};
// 发送 GET 请求
tx.send(cmd).await.unwrap();
// 等待响应结果
let res = resp_rx.await;
println!("GOT = {:?}", res);
});
let t2 = tokio::spawn(async move {
let (resp_tx, resp_rx) = oneshot::channel();
let cmd = Command::Set {
key: "foo".to_string(),
val: b"bar".to_vec(),
resp: resp_tx,
};
// 发送 GET 请求
tx2.send(cmd).await.unwrap();
// 等待响应结果
let res = resp_rx.await;
println!("GOT = {:?}", res)
});
```
最后, 更新管理任务以通过oneshot通道发送响应.
```rust
while let Some(cmd) = rx.recv().await {
match cmd {
Command::Get { key, resp } => {
let res = client.get(&key).await;
// 忽略错误
let _ = resp.send(res);
}
Command::Set { key, val, resp } => {
let res = client.set(&key, val.into()).await;
// 忽略错误
let _ = resp.send(res);
}
}
}
```
在 `oneshot::Sender` 上调用 `send` 会立即完成而不需要 `.await` 操作. 这是因为在 `oneshot` 通道上的 `send` 总是立即失败或者成功,
而没有任何等待.
当接收一半时删除(dropped)了, 在 `oneshot` 通道上发送一个值会返回 `Err` . 这表明接收方不再对响应有兴趣,在我们的方案中, 接收方的取消操作
是可以被接受的事件. `resp.send(...)` 返回的 `Err` 不需要处理.
你可以在 [这里](https://github.com/tokio-rs/website/blob/master/tutorial-code/channels/src/main.rs) 找到完整的代码.
## 背压与通道边界(Backpressure and bounded channels)
每当引用并发或队列时, 最重要的是确保队列是有界的, 且系统会优雅的处理负载. 无界队列最终将占用所有的内存,并导致系统以无法预测的方式发生故障.
Tokio 比较注意避免隐式(无界)队列. 其中很大一部分原因是异步操作是惰性的. 考虑如下代码:
```rust
loop {
async_op();
}
```
如果异步操作非常急切的运行, 在没有确保先前操作已经完成的情况下, loop 循环将会重复入队一个新的 `async_op` 来运行. 这就导致隐式无界队列的产生.
基于回调的系统与基于feature系统尤其容易受到这样的影响.
然而,使用Tokio和异步Rust, 上面的代码片段根本不会运行 `async_op` . 这是因为你没有调用 `.await` . 如果代码片段更新一下变为使用 `.await` ,
则 loop 循环将在重新开始之前等待上一个操作完成.
```rust
loop {
// 不会重复 直到 async_op 操作完成
async_op().await;
}
```
要明确的引用并发与队列,做到这一点的方法包括:
* `tokio::spawn`
* `select!`
* `join!`
* `mpsc::channel`
当这样做时,请确保一定数量的并发总量. 比如说, 在编写TCP接收循环时, 要确保打开的socket链接总数是有界的. 当使用 `mspc::channel` 时, 要选择
一个可管理的通道容量(译者注: 就是要设置一个确定的容量数). 特定的界限值将取决于应用程序.
注意并选择(或设置)良好的边界是编写可靠Tokio应用的重要组成部分.
← [共享状态](SharedState.md)
→ [I/O](IO.md)
================================================
FILE: doc/Framing.md
================================================
## 帧(Framing)
现在,我们将应用刚刚学到的的I/O知识,并以此来实现Mini-Redis的帧层. 形成帧是获取字节流并将其转换为帧流的过程. 帧是两个对等体之间传输数据的单位.
Redis协议帧像下面这样:
```rust
use bytes::Bytes;
enum Frame {
Simple(String),
Error(String),
Integer(u64),
Bulk(Bytes),
Null,
Array(Vec<Frame>),
}
```
注意帧仅由没有任何语义的数据组成. 命令的解析和实现发生在更高的层级.
比如说, HTTP的帧可能看起来像下面这样:
```rust
enum HttpFrame {
RequestHead {
method: Method,
uri: Uri,
version: Version,
headers: HeaderMap,
},
ResponseHead {
status: StatusCode,
version: Version,
headers: HeaderMap,
},
BodyChunk {
chunk: Bytes,
},
}
```
为了去实现Mini-Redis的帧, 我们将实现一个`connection`结构体, 该结构体包装了一个`TcpStream`并读取/写入`mini_redis::Frame`的值.
```rust
use tokio::net::TcpStream;
use mini_redis::{Frame, Result};
struct connection {
stream: TcpStream,
// ... 其它属性字段
}
impl connection {
/// 从Connection中读取一个帧
///
/// 如果 EOF 到达则返回 None
pub async fn read_frame(&mut self)
-> Result<Option<Frame>>
{
// 在这里实现
}
/// 写入一个帧到链接Connection中
pub async fn write_frame(&mut self, frame: &Frame)
-> Result<()>
{
// 在这里实现
}
}
```
你能在[这里](https://redis.io/topics/protocol) 找到完整的Redis协议细节. 完整的 `connection` 代码在 [这里](https://github.com/tokio-rs/mini-redis/blob/tutorial/src/connection.rs) .
## 缓冲区读取(Buffered reads)
`read_frame` 方法在返回之前会等待接收整个帧. 单次调用`TcpStream::read()`方法可能会返回任意数量的数据. 它可能包含一个完整的帧,一部分帧,或者多个帧.
如果接收到部分帧,则会被缓存,并从socket套接字中读取更多的数据. 如果接收到多个帧, 则返回第一个帧,并缓冲其它的数据,直到下一次调用`read_frame`为止.
为了实现这一点, `connection`需要一个读取缓冲区字段. 数据从socket中读取到读缓冲区(read buffer)中. 当一个帧被解析时, 相应的数据就会从缓冲区中移除.
我们将使用 [BytesMut](https://docs.rs/bytes/1/bytes/struct.BytesMut.html) 作为缓冲区(buffer)的类型. 这是一个 [Bytes](https://docs.rs/bytes/1/bytes/struct.Bytes.html) 的可变版本.
```rust
use bytes::BytesMut;
use tokio::net::TcpStream;
pub struct connection {
stream: TcpStream,
buffer: BytesMut,
}
impl connection {
pub fn new(stream: TcpStream) -> connection {
connection {
stream,
// 默认分配buffer容量为4kb
buffer: BytesMut::with_capacity(4096),
}
}
}
```
下一步,我们将实现 `read_frame()` 方法.
```rust
use tokio::io::AsyncReadExt;
use bytes::Buf;
use mini_redis::Result;
pub async fn read_frame(&mut self)
-> Result<Option<Frame>>
{
loop {
// 尝试从buffer数据中解析一个帧. 如果buffer中有足够的数据,那么帧就返回
if let Some(frame) = self.parse_frame()? {
return Ok(Some(frame));
}
// 没有足够的数据读取到一个帧中, 那么尝试从socket中读取更多的数据
// 如果成功了, 一定数据的字节被返回. '0' 表明到了流的末尾
if 0 == self.stream.read_buf(&mut self.buffer).await? {
// 远程关闭了链接,为了彻底关闭, 读缓冲区中应该没有数据了. 如果存在数据, 那说明对等方在发送帧时关闭了socket
return if self.buffer.is_empty() {
Ok(None)
} else {
Err("connection reset by peer".into())
}
}
}
}
```
让我们分解一下. `read_frame` 方循环运行. 首先, `self.parse_frame()` 方法被调用. 这将尝试从`self.buffer` 中解析一个redis帧.
如果这里有足够的数据解析成一个帧, 那么就会返回给`read_frame()`调用者一个帧. 否则的话,我们将尝试从socket中读取更多的数据到缓冲区中.
读取更多数据后,再一次调用`parse_frame()`方法. 这一次, 如果已经接收到足够的数据,那么就能解析成功.
当从流(Stream)中读取时,返回值0表示不再从对等方接收数据. 如果读取缓冲区中任然有数据,则表明已经接收到部分帧,并且链接突然终止了. 这种情况是
一种错误并会返回`Err`.
## `Buf` trait
当从流中读取时, 将调用 `read_buf`方法. 这个版本的read函数采用了一个从 [bytes](https://docs.rs/bytes/) 包中实现了 [BufMut](https://docs.rs/bytes/0.5/bytes/trait.BufMut.html) 的值.
首先,考虑如何使用`read()`实现同样的读取循环. 可以使用`Vec<u8>`来代替`BytesMut`.
```rust
use tokio::net::TcpStream;
pub struct connection {
stream: TcpStream,
buffer: Vec<u8>,
cursor: usize,
}
impl connection {
pub fn new(stream: TcpStream) -> connection {
connection {
stream,
// 分配4kb的缓冲区容量
buffer: vec![0; 4096],
cursor: 0,
}
}
}
```
`connection`上的`read_frame()` 函数.
```rust
use mini_redis::{Frame, Result};
pub async fn read_frame(&mut self) -> Result<Option<Frame>>
{
loop {
if let Some(frame) = self.parse_frame()? {
return Ok(Some(frame));
}
// 确保buffer有容量
if self.buffer.len() == self.cursor {
// 增长buffer
self.buffer.resize(self.cursor * 2, 0);
}
// 读取到缓冲区, 跟踪读取的字节数
let n = self.stream.read(
&mut self.buffer[self.cursor..]).await?;
if 0 == n {
if self.cursor == 0 {
return Ok(None);
} else {
return Err("connection reset by peer".into());
}
} else {
// 更新游标
self.cursor += n;
}
}
}
```
在使用字节数组进行读取时, 我们还必须保持一个游标,来跟踪已经缓冲了多少数据. 我们必须确保缓冲区的空白部分传递给 `read()`. 否则会覆盖缓冲区的数据.
如果缓冲区被填满, 我们必须增加缓冲区来继续读取. 在`parse_frame()`(但不包括)中, 我们还必须解析`self.buffer[..self.cursor]`包含的数据.
因为将字节数据与游标配对非常常见, 所以 `bytes` 包中提供了代表字节数组和游标的抽象. `Buf` trait可以被需要读取数据的类型实现. `BufMut` trait可以被
需要数据写入的类型实现. 当传递一个 `T:BufMut` 到 `read_buf()`时, 缓冲区的内部游标由`read_buf()`自动更新. 因为这一点,在我们的`read_frame`版本中,
我们不需要自己来管理自己的游标.
另外, 当使用 `Vec<u8>` 时缓冲区必须要初始化. `vec![0; 4096]` 分配一个大小为4096字节的数组并在每个位置写0. 当调整buffer的大小时,新的容量也必须要使用0
来初始化. 初始化的过程不是无消耗的. 当使用`BytesMut`和`BufMut`时,容量是**未初始化**的. `BytesMut`抽象阻止了我们读取取未初始化的内存. 这使得我们避免了
初始化的步骤.
## 解析(Parsing)
现在,让我们来看看`parse_frame()`函数. 解析的过程分两步:
1. 确保已经缓冲整个帧并找到帧结束索引.
2. 解析一个帧.
`mini-redis` 包提供给我们一个解决上面两步功能的函数:
1. [Frame::check](https://docs.rs/mini-redis/0.3/mini_redis/frame/enum.Frame.html#method.check)
2. [Frame::parse](https://docs.rs/mini-redis/0.3/mini_redis/frame/enum.Frame.html#method.parse)
我们也将重用`Buf`抽象来得到帮助. 一个`Buf` 传递到`Frame::check`中去. 当`check`函数迭代传入buffer时, 内部的游标也会前进. 当`check`
函数返回时, 缓冲区(buffer)的内部游标会指向帧的末尾.
对于`Buf`的类型,我们使用 [std::io::Cursor<&[u8]>](https://doc.rust-lang.org/stable/std/io/struct.Cursor.html)
```rust
use mini_redis::{Frame, Result};
use mini_redis::frame::Error::Incomplete;
use bytes::Buf;
use std::io::Cursor;
fn parse_frame(&mut self)
-> Result<Option<Frame>>
{
// 创建一个 T:Buf 类型
let mut buf = Cursor::new(&self.buffer[..]);
// 检查是否为一个完整可用的帧
match Frame::check(&mut buf) {
Ok(_) => {
// 得到帧的字节长度
let len = buf.position() as usize;
// 调用parse来重围内部游标
buf.set_position(0);
// 解析帧
let frame = Frame::parse(&mut buf)?;
// 从缓冲区中丢弃帧
self.buffer.advance(len);
// 返回帧的调用者
Ok(Some(frame))
}
// 没有足够数据被缓存的情况
Err(Incomplete) => Ok(None),
// 一个错误被捕获
Err(e) => Err(e.into()),
}
}
```
完整的 [Frame::check](https://github.com/tokio-rs/mini-redis/blob/tutorial/src/frame.rs#L63-L100) 函数代码可在这里找到. 我们不会完全的介绍它.
需要注意的是`Buf`使用了"字节迭代器"风格的API. 它们获取数据并推进游标. 比如, 为了解析一个帧,检查第一个字节来确定帧的类型. 这样的功能使用
[Buf::get_u8](https://docs.rs/bytes/0.6/bytes/buf/trait.Buf.html#method.get_u8) . 它会获取游标位置的字节,并将游标前进一位.
在[Buf](https://docs.rs/bytes/0.6/bytes/buf/trait.Buf.html) trait上还有更多有用的方法. 查看[API docs](https://docs.rs/bytes/0.6/bytes/buf/trait.Buf.html) 来了解更多细节.
## 缓冲写(Buffered writes)
帧相关的API另外一半是`write_frame(frame)`函数. 此函数将整个帧写入到socket中. 为了最小化`write`的系统调用, 写入将先被缓冲. 在写入到socket之前,
将维持一个写缓冲区并将帧编码到此缓冲区.
考虑到大数据量(bulk)的帧流. 要使用`Frame::Bulk(Bytes)`来写入. bulk帧有一个帧头, 它由`$`符后跟数据长度(以字节为单位)组成. 帧的大部分都是`Bytes`值的内容.
如果数据很大,则将其复制到中间缓冲区将会是非常昂贵的操作.
为了实现写缓冲, 我们将使用 [BufWriter struct](https://docs.rs/tokio/0.3/tokio/io/struct.BufWriter.html) . 这个结构体使用`T:AsyncWrite`初始化,
并自身实现了`AsyncWrite`. 当在`BufWriter`上调用`write`时,写操作不会直接传递给内部写程序,而是传递给缓冲区. 当缓冲区满时, 内容会刷新到内部写入器,并清除内部缓冲区.
在某些情况下,还有一些优化可以绕过缓冲区直接写到内部写入器.
在本指引的这部分,我们将不会去尝试实现一个完整的`write_frame()`功能. 完整的实现请查看[这里](https://github.com/tokio-rs/mini-redis/blob/tutorial/src/connection.rs#L159-L184).
首先更新`connection`结构体:
```rust
use tokio::io::BufWriter;
use tokio::net::TcpStream;
use bytes::BytesMut;
pub struct connection {
stream: BufWriter<TcpStream>,
buffer: BytesMut,
}
impl connection {
pub fn new(stream: TcpStream) -> connection {
connection {
stream: BufWriter::new(stream),
buffer: BytesMut::with_capacity(4096),
}
}
}
```
然后, 实现`write_frame()`.
```rust
use tokio::io::{self, AsyncWriteExt};
use mini_redis::Frame;
async fn write_frame(&mut self, frame: &Frame)
-> io::Result<()>
{
match frame {
Frame::Simple(val) => {
self.stream.write_u8(b'+').await?;
self.stream.write_all(val.as_bytes()).await?;
self.stream.write_all(b"\r\n").await?;
}
Frame::Error(val) => {
self.stream.write_u8(b'-').await?;
self.stream.write_all(val.as_bytes()).await?;
self.stream.write_all(b"\r\n").await?;
}
Frame::Integer(val) => {
self.stream.write_u8(b':').await?;
self.write_decimal(*val).await?;
}
Frame::Null => {
self.stream.write_all(b"$-1\r\n").await?;
}
Frame::Bulk(val) => {
let len = val.len();
self.stream.write_u8(b'$').await?;
self.write_decimal(len as u64).await?;
self.stream.write_all(val).await?;
self.stream.write_all(b"\r\n").await?;
}
Frame::Array(_val) => unimplemented!(),
}
self.stream.flush().await;
Ok(())
}
```
此处使用的功能由`AsyncWriteExt`提供. 它们也可以在`TcpStream`上使用,但不建议在没有中间缓冲区的情况下发出单字节写操作.
* [write_u8](https://docs.rs/tokio/0.3/tokio/io/trait.AsyncWriteExt.html#method.write_u8) 写入单个字节到writer上.
* [write_all](https://tokio.rs/tokio/tutorial/framing) 将整个切片写到writer上.
* [write_decimal](https://github.com/tokio-rs/mini-redis/blob/tutorial/src/connection.rs#L225-L238) 由mini-redis来实现.
函数的末尾调用`self.stream.flush().await`. 是因为`BufWriter` 将写操作存储到中间缓冲区上, 因此写调用不能保证将数据写到socket中.
在返回前,我们希望装饰帧写入到socket中. 调用`flush()`将缓冲区中的所有数据写入到socket中.
另外一种二选一的方法是,不在`write_frame()`中调用`flush()`. 而是在`connection`上提供`flush()`函数. 这将允许调用者将队列中的多个小帧
写入到队列, 然后使用系统调用写入将它们全写入到socket中. 但这样做会使用`Coonection`API变得复杂. 简洁是Mini-Redis的目标之一, 因此我们决定在
`fn write_frame()` 中包含`flush().await`的调用.
← [I/O](IO.md)
→ [深入异步](AsyncInDepth.md)
================================================
FILE: doc/Glossary.md
================================================
## 词汇表(Glossary)
### 异步(Asynchronous)
在Rust的上下文中,异步代码指的是使用了`async/await`语言特性的代码,该功能允许很多任务在几个线程(甚至单个线程)上同时运行.
### 并发与并行(Concurrency and parallelism)
并发与并行是两个相关的概念,在谈论到同时执行多个任务时都会使用. 如果某件事并行发生,那么它也是同时发生,但事实上并非如此:在两个任务之间
交替操作,但从来没同时执行这两个任务,这种情况是并发而不是并行.
### Future
Future 是存储某些操作当前状态的值. Future也有轮询方法,该方法使操作可以继续进行,直到需要等待的某些内容(比如网络连接)为止. 调用`poll`方法
应该很快返回.
Future通常可以在异步块中使用`.await`组合多个future来创建.
### 执行器与调度器(Executor/scheduler)
执行器与调度器通过重复调用`poll`方法来执行Future. 标准库中并没有执行器,所以你需要额外的库,并且Tokio的**运行时**提供了使用最广泛的执行器.
执行器可以在几个线程上同时运行大量的Future. 它通过在等待时交换当前正在执行的任务来执行此操作. 如果代码很长时间也没有达到`.await`,则称为
"阻塞了线程"或者"没有回到执行器",这将阻塞其它任务的运行.
### 运行时(Runtime)
**运行时**是一种包含执行程序和与执行程序集成的各种实用程序库,比如计时器程序与IO. 运行时与执行器的名称有时候可以交换来使用. 标准库中没有运行时,
因此你要使用它就得添加额外的库,并且使用最广泛的运行时是Tokio运行时.
运行时也被用在其它上下文中,比如说,"Rust没有运行时" 的短语有时候表示Rust程序的执行没有垃圾回收或者即时编译.
### 任务(Task)
一个任务它是一个运行在Tokio运行时上的操作,它被`tokio::spawn`或者`Runtime::block_on`函数创建. 通过组合它们来创建Future的工具,比如,
`.await`和`join!`不创建新的任务,每个合并的部分都被称为"在同一个任务中".
并行性需要多个任务,但是可以使用比如`join!`之类的工具同时对一项任务执行多个操作.
### 产生(Spawning)
Spawning被表示在使用`tokio::spawn`函数来创建一个新任务. 它在标准库 [std::thread::spawn](https://doc.rust-lang.org/stable/std/thread/fn.spawn.html) 中也指创建新线程.
### 异步块(Async block)
异步块是一种用来创建Future运行某些代码的简便方法. 比如:
```rust
let world = async {
println!("world");
}
let my_future = async {
println!("Hello");
world.await;
}
```
上面的代码创建了一个叫作`my_future`的Future,它会打印出`Hello world!`. 它会首先打印出Hello,然后再运行`world` future. 注意上面的代码
不会自己打印出任何的内容 - 你必须在发生一些事之前实际的执行`my_future`,方法是直接spawning它,或者在有spawning的地方使用`.await`.
### 异步函数(Async function)
与异步块类似,一个异步函数是创建函数体成为future的一种便捷方法. 所有的异步函数都可以被重写为返回future的普通函数:
```rust
async fn do_stuff(i: i32) -> String {
// do stuff
format!("The integer is {}.", i)
}
```
```rust
use std::future::Future;
// 上面的异步函数可以用以下同种方式表述:
fn do_stuff(i: i32) -> impl Future<Output = String> {
async move {
// do stuff
format!("The integer is {}.", i)
}
}
```
这里使用了[impl Trait syntax](https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits) 语法来返回一个future,
因为[Future](https://doc.rust-lang.org/stable/std/future/trait.Future.html)是一个trait. 请注意,因为异步块创建的future在执行之前不会
执行任何操作,因此调用异步函数在返回的future被执行前也不会执行任何操作([ignoring it triggers a warning](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4faf44e08b4a3bb1269a7985460f1923)).
### Yielding
在Rust异步上下文中,yield指允许执行器在单个线程上执行很多的future. future的每一次出让(yield),执行器都会使用另外一个future交换当前future,
通过这种重复的交换当前任务,执行器就可以同时的(并发的)执行大量的任务. future仅能使用`.await`操作符来yield,因此两次`.await`操作之间花费很长时间
的future就可能阻塞其它任务的执行.
具体来说,从[poll](https://doc.rust-lang.org/stable/std/future/trait.Future.html#method.poll) 方法返回时,future就会yields.
### 阻塞(Blocking)
单词 "blocking" (阻塞) 有两种不同的使用方式: "阻塞"的第一层意思是等待某些事完成,"阻塞"的另外一层含义是指当一个future花费很长时间而没有yielding时.
为了明确起见,可以将短语"blocking the thread"(阻塞线程)用于第二种含义.
在Tokio的文档中总是使用第二种"阻塞"的含义.
要在Tokio中运行阻塞的代码,请参考Tokio API指引中的[CPU-bound tasks and blocking code](https://docs.rs/tokio/0.3/tokio/#cpu-bound-tasks-and-blocking-code) 章节.
### 流(Stream)
[Stream](https://docs.rs/tokio/0.3/tokio/stream/trait.Stream.html) 是 [Iterator](https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html) 的异步版本,
并提供值流. 通常与`while let` 循环一起使用,如下所示:
```rust
use tokio::stream::StreamExt; // for next()
while let Some(item) = stream.next().await {
// do something
}
```
单词`stream`有时候用来指 [AsyncRead](https://docs.rs/tokio/0.3/tokio/io/trait.AsyncRead.html) 和 [AsyncWrite](https://docs.rs/tokio/0.3/tokio/io/trait.AsyncWrite.html) 有点令人困惑.
### 通道(Channel)
通道是一种允许一部分代码发送消息到另外一部分的工具. Tokio提供了一些通道,每一种都有其目的与用途.
* [mpsc](https://docs.rs/tokio/0.3/tokio/sync/mpsc/index.html) : 多生产者,单消费者通道. 可以发送许多的值.
* [oneshot](https://docs.rs/tokio/0.3/tokio/sync/oneshot/index.html) : 单生产者,单消费者通道. 能发送单一值.
* [broadcast](https://docs.rs/tokio/0.3/tokio/sync/broadcast/index.html) : 多生产者,多消费者通道. 发送很多值,每一个接收者都能看到每一个值.
* [watch](https://docs.rs/tokio/0.3/tokio/sync/watch/index.html) : 单生产者,多消费者通道,可以发送许多值,但不会保留历史记录. 接收者仅能看到最近的值.
如果你需要使用多生产者,多消费者通道,且仅能有一个消费者能看到每一个消息,你可以使用 [async-channel](https://docs.rs/async-channel/) 包.
还有一些在异步Rust之外使用的通道,比方说 [std::sync::mpsc](https://doc.rust-lang.org/stable/std/sync/mpsc/index.html) 和 [crossbeam::channel](https://docs.rs/crossbeam/latest/crossbeam/channel/index.html) .
这些channels 通过阻塞线程来等待消息,在这异步代码中是不被允许的.
### 背压(Backpressure)
背压是一种用来设计高负载低延迟响应,应用的一种模式. 比如说,`mpsc` 以有界和无界的形式出现. 通过使用有界通道,如果接收方无法及时处理发送方发送的消息数量时,
接收方可以对发送方施加 "背压",这可以避免内存的使用量一直增长不受限制,因为通道上发送的消息越来越多了.
### Actor
一种设计应用程序的设计模式. Actor是指独立产生的任务,该任务代表应用程序的其它部分使用channel与应用程序的另外部分通信来管理某些资源.
有关 actor的示例,请参考 [通道](Channels.md) 章节.
← [指南](../README.md)
================================================
FILE: doc/GracefulShutdown.md
================================================
## 优雅关机(Graceful Shutdown)
这篇文章的目的是告诉你如何在异步应用中正常的关机。
要实现优雅关机一般有3个部分:
* 确定何时关机
* 告诉程序每一部分关闭
* 等待程序的其它部分关闭
文章的剩余部分将介绍这三部分。可以在 [mini-redis](https://github.com/tokio-rs/mini-redis/) 中找到真实世界
如何正确关机的实现,特别是在 [src/server.rs](https://github.com/tokio-rs/mini-redis/blob/master/src/server.rs) 和
[src/shutdown.rs](https://github.com/tokio-rs/mini-redis/blob/master/src/shutdown.rs) 文件中有.
### 确定何时关机(Figuring out when to shut down)
这一点肯定是取决于应用程序,但有一个很关键的标准是应用程序从操作系统接收一个信号。这种情况发生在,当你的应用程序运行在终端时按 `ctrl+c`
时。为了侦探到这个信号,`Tokio` 提供了一个 [tokio::signal::ctrl_c](https://docs.rs/tokio/1/tokio/signal/fn.ctrl_c.html) 函数,
你可以像下面这样来使用它:
```rust
use tokio::signal;
#[tokio::main]
async fn main() {
// ... 产生一个其它任务 task ...
match signal::ctrl_c().await {
Ok(()) => {},
Err(err) => {
eprintln!("Unable to listen for shutdown signal: {}", err);
// 关闭的时候也可能出现错误
},
}
// 发送一个关机信号给应用程序并等待
}
```
如果你有多个关机条件,你可以使用 [mpsc channel](https://docs.rs/tokio/1/tokio/sync/mpsc/index.html) 来将关机信息发送到一个地方。
然后你可以在channel上通过 [Select](https://docs.rs/tokio/1/tokio/macro.select.html) 匹配到 `ctrl_c` 信号。比如像下面这样:
```rust
use tokio::signal;
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
let (shutdown_send, shutdown_recv) = mpsc::unbounded_channel();
// ... 产生一个其它任务 task ...
//
// application uses shutdown_send in case a shutdown was issued from inside
// 应用使用 shutdown_send 发出关机信息来防止应用从内部关闭
// the application
tokio::select! {
_ = signal::ctrl_c() => {},
_ = shutdown_recv.recv() => {},
}
// 发送一个关机信号给应用程序并等待
}
```
### 告之关机的一些事情(Telling things to shut down)
告诉应用程序每一部分关闭时常用的工具是 [broadcast channel](https://docs.rs/tokio/1/tokio/sync/broadcast/index.html) 。
想法其实很简单,应用程序中的每一个任务都有一个广播(broadcast) 通道(channel)接收器,当消息在channel上广播时,任务会自行关闭。通常,
使用 [tokio::select](https://docs.rs/tokio/1/tokio/macro.select.html) 来接收这个广播消息。比如在 `mini-redis` 的每一个
任务中来接收 `shutdown` 消息的方式:
```rust
let next_frame = tokio::select! {
res = self.connection.read_frame() => res?,
_ = self.shutdown.recv() => {
// If a shutdown signal is received, return from `run`.
// 如果一个 shutdown 信号被接收到,将从 `运行` 状态返回,并将导致此任务终止.
// This will result in the task terminating.
return Ok(());
}
};
```
在 `mini-redis` 的示例中,当一个关机信号被接收到时,task(任务)会立即终止,但有时候你需要在终止任务之前运行一个`关机过程`。比方说,
有时候你需要在关机前将数据刷到一个文件或数据库中,或者有任务管理的链接,你可能想在任务终止前在链接上发送关机消息。
有一个很好的方式是,将 `broadcast channel` 包装到一个 struct 中。这里有一个示例 [这里](https://github.com/tokio-rs/mini-redis/blob/master/src/shutdown.rs) 。
值得一提的是你也可以使用 [watch channel](https://docs.rs/tokio/1/tokio/sync/watch/index.html) 来达到同样的效果。这两种方式之间没有明显的差异。
### 等待一些事情完成关闭(Waiting for things to finish shutting down)
一旦你告诉另一个任务要关闭时,你需要等待它们完成。最简单的方法是使用 [mpsc channel](https://docs.rs/tokio/1/tokio/sync/mpsc/index.html)
这里不是发送消息,而是等待通道的关闭,这时每一个sender都会被丢弃。
下面是上面这种方式的简单示例,示例生成10个任务,然后使用 `mpsc` 通道等待它们关闭。
```rust
use tokio::sync::mpsc::{channel, Sender};
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
let (send, mut recv) = channel(1);
for i in 0..10 {
tokio::spawn(some_operation(i, send.clone()));
}
// 等待此任务task完成
//
// We drop our sender first because the recv() call otherwise
// 我们丢弃drop掉 sender 是因为 recv()的调用,不然的话将会一直休眠
// sleeps forever.
drop(send);
// When every sender has gone out of scope, the recv call
// 当每个 sender 超过作用域时,recv 的调用将返回error。这里我们忽略它。
// will return with an error. We ignore the error.
let _ = recv.recv().await;
}
async fn some_operation(i: u64, _sender: Sender<()>) {
sleep(Duration::from_millis(100 * i)).await;
println!("Task {} shutting down.", i);
// sender 离开了作用域 ...
}
```
有个很重要的点是,等待关闭的任务都持有一个sender. 在这种情况下你必须确保等待通道关闭之前删除此sender。
← [同步代码桥接](BridgingWithSyncCode.md)
→ [词汇表](Glossary.md)
================================================
FILE: doc/HelloTokio.md
================================================
## 你好 Tokio(Hello Tokio)
我们将通过编写一个非常基础的Tokio应用来开始. 它可以连接到Mini-Redis服务, 并设置键 `hello` 的值为 `world` . 然后它可以读取这个键, 这将
使用Mini-Redis客户端完成.
## 实现代码(The code)
### 生成一个新的包
让我们创建一个新的Rust app:
```shell script
cargo new my-redis
cd my-redis
```
### 添加依赖(Add dependencies)
下一步, 打开 `Cargo.toml` 并在 `[dependencies]` 下添加如下依赖:
```shell script
tokio = {version = "0.2", features = ["full"]}
mini-redis = "0.2"
```
### 编写代码(Write the code)
然后, 打开 `main.rs` 并使用如下内容替换:
```rust
use mini_redis::{client, Result};
#[tokio::main]
pub async fn main() -> Result<()> {
// 打开链接到mini-redis的链接
let mut client = client::connect("127.0.0.1:6379").await?;
// 设置 "hello" 键的值为 "world"
client.set("hello", "world".into()).await?;
// 获取"hello"的值
let result = client.get("hello").await?;
println!("got value from the server; result={:?}", result);
Ok(())
}
```
确保 Mini-Redis服务正在运行. 在另外一个终端窗口中运行:
```shell script
mini-redis-server
```
现在, 运行 `my-redis` 应用:
```shell script
cargo run
got value from the server; result=Some(b"world")
```
成功了!
你可以在 [这里](https://github.com/tokio-rs/website/blob/master/tutorial-code/hello-tokio/src/main.rs) 找到完整的代码.
## 过程分解(Breaking it down)
让我们花一点时间来回顾一下刚刚上面做的事情. 这里没有太多的代码, 但是却发生了很多事.
```rust
let mut client = client::connect("127.0.0.1:6379").await?;
```
`client::connect` 函数功能由 `mini-redis` 包提供. 它通过一个指定的远程地址异步的建立一个TCP链接. 一旦链接被建立, 一个 `client` 处理器就会被返回.
即使操作是异步的,但我们写的代码看起来是同步的. 操作是异步的唯一标识是 `.await` 操作符.
### 什么是异步编程?(what is asynchronous programming?)
大多数计算机程序的执行顺序都是按程序编写的顺序来执行的. 第一行代码先执行,然后是下一行,这样一直下去. 对于同步编程, 当程序遇到不能立即完成的操作时,
它将被阻塞直到该操作完成为止. 比方说, 建立TCP链接需要对等方通过网络进行交换, 这一过程可能需要相当长的时间, 期间线程是阻塞的.
对于异步编程不能立即完成的操作会被挂起到后台. 当前线程不会被阻塞, 并且能继续运行其它的事. 一旦操作完成, 任务将从中断处继续处理. 我们前面的示例
只有一个任务, 因此在挂起时什么也没发生, 但是通常异步程序有很多这样的任务.
尽管异步编程可以使得应用程序更快, 但它通常也会导致程序复杂的多. 一旦异步操作完成, 就需要程序员跟踪恢复工作所需的所有状态. 从历史角度来看, 这是一个非常
乏味且容错出错的任务.
### 编译时绿色线程(Compile-time green-threading)
Rust使用被叫作 `async/await` 的feature实现异步编程. 执行异步操作的函数用 `async` 关键字来标记. 在我们的示例当中, `connect` 函数被定义像下面这样:
```rust
use mini_redis::Result;
use mini_redis::client::Client;
use tokio::net::ToSocketAddress;
pub async fn connect<T: ToSocketAddress>(addr: T) -> Result<Client> {
// ...
}
```
使用 `async fn` 的定义看起来像同步函数一样, 但是操作是异步的. Rust在编译时将转换 `async fn` 为异步操作. 任何在 `async fn` 中的 `.await`
调用都会将控制权返回给线程. 在后台进行操作时, 线程可能会做其它的工作.
```text
尽管其它语言也实现了 `async/await`, 但是Rust的实现比较独特. 主要是Rust的异步操作是惰性(lazy)的. 结果就是导致与其它语言不同的运行时语义.
```
如果这样说还不太明白其意义,请不用担心. 我们将在本指南中进一步探讨 `async/await` .
### 使用 `async/await`(Using `async/await` )
异步函数的调用与其它Rust函数一样. 但是调用这些函数不会导致函数体执行(译者注: 即是一种声明不会立即执行). 而是调用这些函数返回表示操作的值.
从概念上讲,这类似于零参数闭包. 为了实际的去运行这些操作, 你应该使用 `.await` 操作符来返回值.
通过下面的程序举例:
```rust
async fn say_world() {
println!("world");
}
#[tokio::main]
async fn main() {
// 调用 say_world函数没有立即执行 say_world() 函数体
let op = say_world();
// 这里会首先打印
println!("hello");
// 调用 .await 操作才会执行say_world
op.await;
}
```
输出:
```text
hello
world
```
`async fn` 返回值是一个实现了 `Future` trait的异步类型.
### 异步 `main` 函数(Async `main` function)
main 函数用来启动应用, 它与大多数Rust其它包中的函数不同:
* 1. 它是一个 `async fn` .
* 2. 它使用 `#[tokio::main]` 注解.
我们要进入一个异步上下文时,一个 `async fn` 被使用. 但是异步函数必须由一个运行时 [runtime](https://docs.rs/tokio/0.2/tokio/runtime/index.html) 来执行.
运行时包含异步任务调度器(scheduler), 提供I/O事件, 计时器(timers)等等. 运行时不会自动开始,因此需要main函数来启动它.
`#[tokio::main]` 函数宏. 它将 `async fn main()` 转换为一个初始化一个运行时实例且执行异步main函数的 同步 `fn main()`.
比如说下面的示例:
```rust
#[tokio::main]
async fn main() {
println!("hello");
}
```
转换后的结果:
```rust
fn main() {
let mut rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
println!("hello");
})
}
```
tokio运行时的细节将在后面介绍.
### Cargo 特性(Cargo features)
当本教程中的Tokio依赖,启用了 `full` 特性时:
```toml
tokio = {version = "0.2", features = ["full"]}
```
Tokio有很多功能(TCP,UDP,Unix 套接字, 定时器(timers), 同步工具(sync utilities),多种调度器类型(multiple scheduler types), 等等).
不是所有的应用都需要所有的功能. 当我们尝试去优化编译时间或者应用占用空间时, 应用程序可以去选择仅仅它需要使用的特性.
比如现在我们在tokio的依赖中使用了 "full" 的特性.
← [指南](Introduction.md)
→ [Spawning](Spawning.md)
================================================
FILE: doc/IO.md
================================================
## I/O
在Tokio中的I/O操作几乎与`std`的相同, 但是(Tokio中的I/O操作是)是异步的. 这里有关于读( [AsyncRead](https://docs.rs/tokio/0.2/tokio/io/trait.AsyncRead.html) )和写( [AsyncWrite](https://docs.rs/tokio/0.2/tokio/io/trait.AsyncWrite.html) )的trait. 特定的类型适当的实现了这些trait, 比如:( [TcpStream](https://docs.rs/tokio/0.2/tokio/net/struct.TcpStream.html), [File](https://docs.rs/tokio/0.2/tokio/fs/struct.File.html), [Stdout](https://docs.rs/tokio/0.2/tokio/io/struct.Stdout.html) ) `AsyncRead` 与 `AsyncWrite` 也可以通过许多数据结构来实现, 例如, `Vec<u8>` 和 `&[u8]` . 这允许在需要一个reader与writer的地方使用字节数组.
在本章节页中, 会介绍使用Tokio来进行基本的I/O读写操作过程, 并通过一些示例进行介绍. 在下一页中我们将获得更多的关于I/O操作的高级示例.
## `异步读` 和 `异步写` (`AsyncRead` and `AsyncWrite`)
这两个trait为异步读和写入字节流提供了便利性. 这两个trait中的方法通常不能直接的调用, 就好像你不能从`Future` trait中手动的调用 `poll` 方法一样.
取而代之是, 你将通过 `AsyncReadExt` 与 `AsyncWriteExt` 提供的实用程序方法来使用它.
让我们简单的看看其中的几个方法. 所有的方法都是异步的且必须使用 `.await`.
### `async fn read()`
[AsyncReadExt::read](https://docs.rs/tokio/0.2/tokio/io/trait.AsyncReadExt.html#method.read) 提供了一个异步的用来读取数据到缓冲区中的方法,并返回读取的字节数.
**注意** : 当 `read()` 返回 `Ok(0)` 时, 这表明流已被关闭了. 对 `read()` 的任何其它的调用将立即返回`Ok(0)`完成. 对于 [TcpStream](https://docs.rs/tokio/0.2/tokio/net/struct.TcpStream.html) 实例, 这表明socket的读取部分已经关闭.
```rust
use tokio::fs::File;
use tokio::io::{self, AsyncReadExt};
#[tokio::main]
async fn main() -> io::Result<()> {
let mut f = File::open("foo.txt").await?;
let mut buffer = [0; 10];
// 读取10个字节
let n = f.read(&mut buffer[..]).await?;
println!("The bytes: {:?}", &buffer[..n]);
Ok(())
}
```
### `async fn read_to_end()`
[AsyncReadExt::read_to_end](https://docs.rs/tokio/0.2/tokio/io/trait.AsyncReadExt.html#method.read_to_end)
从流中读取所有的字节直到遇到 EOF.
```rust
use tokio::io::{self, AsyncReadExt};
use tokio::fs::File;
#[tokio::main]
async fn main() -> io::Result<()> {
let mut f = File::open("foo.txt").await?;
let mut buffer = Vec::new();
// 读取整个文件
f.read_to_end(&mut buffer).await?;
Ok(())
}
```
### `async fn write()`
[AsyncWriteExt::write](https://docs.rs/tokio/0.2/tokio/io/trait.AsyncWriteExt.html#method.write) 将缓冲区中的数据写入到writer
并返回写入的字节数.
```rust
use tokio::io::{self, AsyncWriteExt};
use tokio::fs::File;
#[tokio::main]
async fn main() -> io::Result<()> {
let mut file = File::create("foo.txt").await?;
// 写入字字节符串的一些前缀, 但不一定是全部
let n = file.write(b"some bytes").await?;
println!("Write the first {} bytes of 'some bytes'.", n);
Ok(())
}
```
### `async fn write_all()`
[AsyncWriteExt::write_all](https://docs.rs/tokio/0.2/tokio/io/trait.AsyncWriteExt.html#method.write_all) 将整个缓存区写入到writer.
```rust
use tokio::io::{self, AsyncWriteExt};
use tokio::fs::File;
#[tokio::main]
async fn main() -> io::Result<()>{
let mut buffer = File::create("foo.txt").await?;
buffer.write_all(b"some bytes").await?;
Ok(())
}
```
这两个trait都包含了其它有用的方法. 有关完整的列表, 请参考API文档.
## 辅助函数(Helper functions)
另外, 与 `std` 包中一样, `tokio::io`模块也包含了一些有用的实用函数和用于处理标准输入,输出,错误的API. [standard input](https://docs.rs/tokio/0.2/tokio/io/fn.stdin.html),
[standard output](https://docs.rs/tokio/0.2/tokio/io/fn.stdout.html), [standard error](https://docs.rs/tokio/0.2/tokio/io/fn.stderr.html) .
比如, `tokio::io::copy` 可以异步将reader中的全部内容复制到writer中去.
```rust
use tokio::fs::File;
use tokio::io;
#[tokio::main]
async fn main() -> io::Result<()> {
let mut reader: &[u8] = b"hello";
let mut file = File::create("foo.txt").await?;
io::copy(&mut reader, &mut file).await?;
Ok(())
}
```
注意, 这利用了字节数组也实现了 `AsyncRead` 这一特点.
## 回声服务器(Echo server)
让我们练习一些异步I/O. 我们将编写一个回声服务.
此回声服务绑定一个 `TcpListener` 且在一个循环中接收入站链接. 对于每个链接将从socket中读取数据并将数据立即写回到socket中.
客户端发送数据到服务端并接收回同样的返回.
我们将使用略微不同的策略来两次实现echo服务.
### 使用 `io::copy()` (Using `io::copy()`)
首先,我们将使用`io::copy()` 实现echo的逻辑部分.
这是一个TCP服务,需要一个accept循环. 产生一个任务来处理每一个被接收的Socket链接.
```rust
use tokio::io;
use tokio::net::TcpListener;
#[tokio::main]
async fn main() -> io::Result<()> {
let mut listener = TcpListener::bind("127.0.0.1:6124").await.unwrap();
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
// 这里Copy数据
});
}
}
```
和上面看到的一样, 这个实用函数需要一个reader和一个writer并从它们中的一个复制数据到另外一个中去. 然而, 我们只有一个`TcpStream`.
该单一值同时实现了`AsyncReader`和`AsyncWrite`. 因为`io::copy`的reader和writer都需要`&mut`, 所有socket不能同时用于两个参数.
```rust
// 这样无法编译
io::copy(&mut socket, &mut socket).await?;
```
### 拆分reader与writer(Splitting a reader + writer)
为了解决这个问题, 我们必须分割socket到一个reader处理器与一个writer处理器中去. 拆分一个reader/writer组合最佳的方法依赖一个特定的类型.
任何reader+writer类型都能被 `io::split` 工具拆分. 这个函数传入单个值,并返回单独的reader和writer处理器. 这两个处理器可以单独的使用,
包括从不同的任务中.
比如, echo客户端能像下面这样处理并发读与写:
```rust
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
#[tokio::main]
async fn main() -> io::Result<()> {
let socket = TcpStream::connect("127.0.0.1:6124").await?;
let (mut rd, mut wr) = io::split(socket);
// 在后台写入数据
let write_task = tokio::spawn(async move {
wr.write_all(b"hello\r\n").await?;
wr.write_all(b"world\r\n").await?;
// 有时候Rust的推导需要一点帮助
Ok::<_, io::Error>(())
});
let mut buf = vec![0; 128];
loop {
let n = rd.read(&mut buf).await?;
if n == 0 {
break;
}
println!("GOT {:?}", &buf[..n]);
}
Ok(())
}
```
因为 `io::split` 支持任意实现了`AsyncRead+AsyncWrite` 类型的值且返回独立的处理器, `io::split`内部使用了`Arc`与`Mutex`. 使用`TcpStream`可以避免这种开销, `TcpStream` 提供了两个专门的拆分函数.
[TcpStream::split](https://docs.rs/tokio/0.2/tokio/net/struct.TcpStream.html#method.split) 引用流并返回一个reader和writer的处理器.因为使用了引用,所以两个处理器都必须保持与调用`split()`相同的任务一致. 这个特殊的`split`是零成本的. 这里不需要`Arc`或者`Mutex`. `TcpStream`也提供了一个 [into_split](https://docs.rs/tokio/0.2/tokio/net/struct.TcpStream.html#method.into_split) 功能,此功能支持仅需要`Arc`就能跨任务移动处理器.
因为`io::copy()`在属于`TcpStream`的同一个任务上被调用,所以我们可以使用 [TcpStream::split](https://docs.rs/tokio/0.2/tokio/net/struct.TcpStream.html#method.split).处理echo逻辑服务的任务变为:
```rust
tokio::spawn(async move{
let(mut rd, mut wr) = socket.split();
if io::copy(&mut rd, &mut wr).await.is_err() {
eprintln!("failed to copy");
}
});
```
你可以 [这里](https://github.com/tokio-rs/website/blob/master/tutorial-code/io/src/echo-server.rs) 找到完整的代码.
### 手动复制(Manual copying)
现在让我们来看看如何通过手动的复制数据来编写echo服务器. 为了做到这一点,我们使用 [AsyncReadExt::read](https://docs.rs/tokio/0.2/tokio/io/trait.AsyncReadExt.html#method.read)
和 [AsyncWriteExt::write_all](https://docs.rs/tokio/0.2/tokio/io/trait.AsyncWriteExt.html#method.write_all) .
完整的echo服务像下面这样:
```rust
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() -> io::Result<()> {
let mut listener = TcpListener::bind("127.0.0.1:6124").await.unwrap();
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
let mut buf = vec![0; 1024];
loop {
match socket.read(&mut buf).await {
// 返回 Ok(0) 值标识远程链接已关闭.
Ok(0) => return,
Ok(n) => {
// 复制数据到socket中
if socket.write_all(&buf[..n]).await.is_err() {
// 未期待的socket错误, 这里我们不做什么,因此停止处理.
return;
}
}
Err(_) => {
// 未期待的socket错误, 这里我们不做什么,因此停止处理.
return;
}
}
}
});
}
}
```
让我们分解一下上面的过程. 首先, 由于使用了`AsyncRead`和`AsyncWrite`, 其扩展的trait必须要被引入到范围内.
```rust
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
```
(译者注: 上面有说过,我们仅能使用其扩展的trait)
### 分配一个缓冲区(Allocating a buffer)
有种策略是从socket中读取一些数据到buffer(缓冲区)中,然后将缓冲区的内容写回到socket中去.
```rust
let mut buffer = vec![0;1024];
```
要明确的避免栈缓冲区. 回想一下 [之前的](./Spawning.md) (中的Send边界), 所有通过对`.await`调用存活的任务数据都必须由任务本身存储.
在这种情况下,将在`.await`的调用中使用`buf`. 所有的任务数据都被存储在一个分配中. 你可以将其看作一个枚举, 其中每个变量体都是为特定调用
`.await`而需要存储的数据.
如果buffer由栈数组来表示, 那么每一个接受socket产生的任务的内部结构可能类似于:
```rust
struct Task {
// 内部的任务字段
task: enum {
AwaitingRead {
socket: TcpStream,
buf: [BufferType],
},
AwaitingWriteAll {
socket: TcpStream,
buf: [BufferType],
}
}
}
```
如果栈数组被使用来作来buffer的类型, 它将以 _内联_ 的方式存储在任务结构中. 这将使用任务本身的结构变得非常大. 另外缓冲区buffer的大小通常是
页面大小. 反过来,这会使用任务(Task)大小变得很臃肿: `$page-size + a-few-bytes`.
编译器对异步结块布局的优化比基本的`enum`(枚举)更加好. 实际上,变量不会像枚举那样在变体中移动. 但是,任务结构体的大小至少与最大变量一样大.
### 处理 EOF(Handling EOF)
(译者注: EOF: "end of file" 的缩写, 表示 "文字流结尾" 这种流(Stream) 可以是文件,也可以是标准输入. 一般理解为流的结束标识)
当读取TCP流的一半时关闭了, 调用`read()`会返回`Ok(0)`. 以这一点来退出循环是很重要的. 忘记以EOF标识来跳出循环是bug的常见来源方式.
```rust
loop {
match socket.read(&mut buffer).await {
// 返回值是 Ok(0) 标志, 表示远端已经关闭
Ok(0) => {
// 其它处理
}
}
}
```
忘记以EOF标识来跳出循环的结果就是会造成CPU 100%循环占用. 关闭socket后, `socket.read()` 会立即返回. 然后循环会一直重复下去.
完整的代码参考 [这里](https://github.com/tokio-rs/website/blob/master/tutorial-code/io/src/echo-server.rs)
← [通道(Channels)](Channels.md)
→ [帧(Framing)](Framing.md)
================================================
FILE: doc/Introduction.md
================================================
## 指南(Tutorial)
这篇教程将一步步带你了解构建 [Redis](https://redis.io/) 客户端与服务端的过程. 我们将从使用Rust异步编程的基础开始. 我们将实现一个Redis
命令的子集并且获得关于Tokio的全方位的了解.
## 迷你-Redis(Mini-Redis)
你将在本教程中构建的工程可以在 [Mini-Redis on GitHub](https://github.com/tokio-rs/mini-redis) 上获得. Mini-Redis被设计的主要目的是
为了学习tokio,这种方式备受好评, 但这也意味着Mini-Redis缺少你想要的真实Redis库中的一些功能与特性. 你可以在这里 [crates.io](https://crates.io/)
上找到生产级可用Redis库.
我们将在本教程中直接使用Mini-Redis. 这使得我们在教程的后面实现部分之前,可以使用Mini-Redis的部分功能.
## 获取帮助(Getting Helping)
在任何时候, 如果你的学习被卡住, 你总能在 [Discord](https://discord.gg/tokio) 或 [Github discussions](https://github.com/tokio-rs/tokio/discussions) 上得到帮助.
不必担心问一些 **"初学者"** 问题. 我们都是从某个地方开始的,并且乐于提供帮助.
## 前提条件(Prerequisites)
读者应该已经熟悉了 [Rust](https://rust-lang.org/) . [Rust book](https://doc.rust-lang.org/book/) 是入门的绝佳资源.
虽然不是必须的, 但是你有Rust标准库或其它语言编写网络代码的一些经验的话,那将会有所帮助.
Redis相关的知识不是必要的.
## Rust
在我们开始之前, 你应该确保你已安装了 [Rust](https://www.rust-lang.org/tools/install) 工具链并做好了准备. 如果你没有做好这些, 使用
[rustup](https://rustup.rs/) 来安装是一个很好的方式.
本教程需要的最小Rust版本是 `1.39.0` , 但是推荐最好是最近的 Rust stable 版本.
为了检查Rust已经安装到你的电脑上, 执行如下命令查看:
```shell script
rustc --version
```
你应该会看到像 `rustc 1.43.1 (8d69840ab 2020-05-04)` 这样的输出.
## 迷你Redis服务(Mini-Redis server)
接下来安装Mini-Redis 服务. 它被用来测试我们即将要构建的客户端.
```shell script
cargo install mini-redis
```
执行下面的命令确保服务启动与安装成功了:
```shell script
mini-redis-server
```
然后尝试使用 `mini-redis-cli` 来得到键 `foo` 的值:
```shell script
mini-redis-cli get foo
```
你应该会看了 `nil` .
## 准备开始(Ready to go)
现在一切都作好了准备. 去到下一章节你将编写你的第一个Rust异步应用.
→ [你好 Tokio](HelloTokio.md)
================================================
FILE: doc/Select.md
================================================
## Select
到目前为止,当我们想向系统添加并发时,我们会产生一个新的任务(task). 现在我们将介绍使用Tokio来并发执行异步代码的其它方法.
## `tokio::select!`
`tokio::select!` 宏允许等待多个异步计算且当单个计算完成时返回(译者注: 多个并发或并行异步计算任务,返回最先完成的那个).
比如说:
```rust
use tokio::sync::oneshot;
#[tokio::main]
async fn main() {
let (tx1, rx1) = oneshot::channel();
let (tx2, rx2) = oneshot::channel();
tokio::spawn(async {
let _ = tx1.send("one");
});
tokio::spawn(async {
let _ = tx2.send("two");
});
tokio::select! {
val = rx1 => {
println!("rx1 completed first with {:?}", val);
}
val = rx2 => {
println!("rx2 completed first with {:?}", val);
}
}
}
```
使用了两个 `oneshot` 通道. 其中任一通道都能先完成. `select!` 语句在两个channels上等待,并将`va1`绑定到任务返回的值上. 当其中任一 `tx1` 或者
`tx2` 完成时,与之相关的块就会执行.
另外没有被完成的分支将会被丢弃(dropped). 在上面的示例中,计算正在每个channel的 `oneshot::Receiver` 上等待. 没有完成的`oneshot::Receiver`
channel将会被丢弃.
### 取消(Cancellation)
对于异步Rust来说,取消操作是通过删除一个future来完成的. 回顾一下 [深入异步](AsyncInDepth.md) 章节中,使用future来实现Rust的异步操作且
future是惰性的. 仅仅当future被轮询时操作才会处理. 如果future被删除(丢弃),操作就不会继续,因为与之所有相关联的状态都被丢弃了.
也说是说,有时候异步操作将产生后台任务或者启动在后台运行的其它操作. 比方说,在上面的示例中,产生一个任务将消息发送回去. 一般来说这个任务会执行
一些计算来生成值.
Futures或者其它类型能通过实现 `Drop` 去清理后台资源. Tokio的`oneshot::Receiver`通过向`Sender`方发送一个关闭的通知来实现`Drop`功能.
Sender方能接收到这个通知并通过丢弃正在进行的操作来中止它.
```rust
use tokio::sync::oneshot;
async fn some_operation() -> String {
// 这里计算值
}
#[tokio::main]
async fn main() {
let (mut tx1, rx1) = oneshot::channel();
let (tx2, rx2) = oneshot::channel();
tokio::spawn(async {
// select 操作和 oneshot 的 `close()` 通知.
tokio::select! {
val = some_operation() => {
let _ = tx1.send(val);
}
_ = tx1.closed() => {
// `some_operation()` 被调用,
// 任务完成且 `tx1` 被丢弃
}
}
});
tokio::spawn(async {
let _ = tx2.send("two");
});
tokio::select! {
val = rx1 => {
println!("rx1 completed first with {:?}", val);
}
val = rx2 => {
println!("rx2 completed first with {:?}", val);
}
}
}
```
### `Future`的实现(The `Future` implementation)
为了帮助更好的理解`select!`是如何工作的,让我们看看假想的Future实现像什么样子. 这是一个简单的版本. 在具体的实践中,`select!`还包括其它的功能,
比如随机选择要首先轮询的分支.
```rust
use tokio::sync::oneshot;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
struct MySelect {
rx1: oneshot::Receiver<&'static str>,
rx2: oneshot::Receiver<&'static str>,
}
impl Future for MySelect {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if let Poll::Ready(val) = Pin::new(&mut self.rx1).poll(cx) {
println!("rx1 completed first with {:?}", val);
return Poll::Ready(());
}
if let Poll::Ready(val) = Pin::new(&mut self.rx2).poll(cx) {
println!("rx2 completed first with {:?}", val);
return Poll::Ready(());
}
Poll::Pending
}
}
#[tokio::main]
async fn main() {
let (tx1, rx1) = oneshot::channel();
let (tx2, rx2) = oneshot::channel();
// use tx1 and tx2
MySelect {
rx1,
rx2,
}.await;
}
```
`MySelect` future 包含每个分支的future. 当`MySelect`被轮询时,第一个分支被轮询. 如果它是ready状态,就使用它的值且`MySelect`完成.
然后`.await`接收到来着future的输出,future被删除. 结果就是两个分支的future都被删除. 因为一个分支未完成,因此操作被有效取消.
记住来自上一章节的话:
```markdown
当一个future返回Poll::Pending时,它**必须**确保在future的某个时候向waker发送信号. 忘记这样做会导致任务无限被挂起.
```
`MySelect`的实现中没有显示的使用`Context`的参数. 取代的是,通过将`cx`传递给内部future来满足waker的要求. 由于内部future也必须满足waker的
要求,因此在收到来自内部future的`Poll::Pending`时仅返回`Poll::Pending`. 所以`MySelect`也满足waker的要求.
## 语法(Syntax)
`select!`宏能处理超过2个以上的分支. 当前最大限制64个分支. 每个分支的结构像下面这样:
```text
<pattern> = <async expression> => <handler>,
```
当`select!`宏展开时,所有的`<async expression>`都会被汇总并同时执行. 当其中一个表达式完成时,结果就会被匹配到`<pattern>`. 如果结果与
pattern匹配时,那么将删除所有剩余的异步表达式并执行`<handler>`. `<handler>`表达式可以访问被`<pattern>`建立的任何绑定值.
基本上`<pattern>`就是变量名,异步表达式的结果可以绑定到这个变量名上且`<handler>`可以访问这个变量. 这就是为什么最开始的示例中,`va1`能被
`<pattern>`使用且`<handler>`能访问`va1`.
如果`<pattern>`与异步计算的结果不匹配,则其余的异步表达式将继续并发执行直到下一个完成为止. 这时,将相同的逻辑用于该结果.
因为`select!`可以采用任意的异步表达式,所以可以在定义复杂的计算时来选择它.
在这里,我们选择`oneshot` channel和TCP链接的输出.
```rust
use tokio::net::TcpStream;
use tokio::sync::oneshot;
#[tokio::main]
async fn main() {
let (tx, rx) = oneshot::channel();
// 产生一个任务来发送消息到oneshot 中
tokio::spawn(async move {
tx.send("done").unwrap();
});
tokio::select! {
socket = TcpStream::connect("localhost:3465") => {
println!("Socket connected {:?}", socket);
}
msg = rx => {
println!("received message first {:?}", msg);
}
}
}
```
在这里,我们选择一个`oneshot`并接收来自`TcpListener`的socket套接字.
```rust
use tokio::net::TcpListener;
use tokio::sync::oneshot;
use std::io;
#[tokio::main]
async fn main() -> io::Result<()> {
let (tx, rx) = oneshot::channel();
tokio::spawn(async move {
tx.send(()).unwrap();
});
let mut listener = TcpListener::bind("localhost:3465").await?;
tokio::select! {
_ = async {
loop {
let (socket, _) = listener.accept().await?;
tokio::spawn(async move { process(socket) });
}
// 帮助Rust的类型推导
Ok::<_, io::Error>(())
} => {}
_ = rx => {
println!("terminating accept loop");
}
}
Ok(())
}
```
accept循环一直运行,直到遇到错误或`rx`接到到值为止. `_`表示我们对异步计算返回的值不感兴趣.
## 返回值(Return value)
`tokio::select!`宏返回`<handler>`表达式的结果.
```rust
async fn computation1() -> String {
// 计算1
}
async fn computation2() -> String {
// 计算2
}
#[tokio::main]
async fn main() {
let out = tokio::select! {
res1 = computation1() => res1,
res2 = computation2() => res2,
};
println!("Got = {}", out);
}
```
因为这一点,它需要`<handler>`表达式每个分支返回的值相同. 如果`select!`表达式的输出不是必须的,推荐将表达式的返回值类型为 `()`.
## 错误(Errors)
使用`?`号操作符从表达式传播错误. 它如何工作是取决于是否`?`号从异步表达式或处理程序中使用. 使用`?`在异步表达式中能将错误传播到异步表达式之外.
这就使异步表达式的输出成一个`Result`了. 从一个处理程序使用`?`号能立即传播错误到`select!`表达式之外. 让我们再次来看看accept 循环:
```rust
use tokio::net::TcpListener;
use tokio::sync::oneshot;
use std::io;
#[tokio::main]
async fn main() -> io::Result<()> {
// [设置 `rx` oneshot channel]
let listener = TcpListener::bind("localhost:3465").await?;
tokio::select! {
res = async {
loop {
let (socket, _) = listener.accept().await?;
tokio::spawn(async move { process(socket) });
}
// 帮助Rust类型推导
Ok::<_, io::Error>(())
} => {
res?;
}
_ = rx => {
println!("terminating accept loop");
}
}
Ok(())
}
```
注意`listener.accept().await?`. `?`号操作符传播错误到表达式之外且和`res`绑定. 如果是一个错误, `res`将被设置为`Err(_)`. 当然在handler内部
`?`可以再次使用. `res?` 声明将传播一个错误到`main`函数之外.
## 模式匹配(Pattern matching)
回顾一下`select!`宏的分支语法定义:
```text
<pattern> = <async expression> = <handler>,
```
到目前为止,我们仅仅对`<pattern>`使用了变量绑定. 然而,这里能使用任何Rust模式. 比如说,假设我们从多个 MPSC 通道接收,我们可能会执行以下操作:
```rust
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
let (mut tx1, mut rx1) = mpsc::channel(128);
let (mut tx2, mut rx2) = mpsc::channel(128);
tokio::spawn(async move {
// Do something w/ `tx1` and `tx2`
});
tokio::select! {
Some(v) = rx1.recv() => {
println!("Got {:?} from rx1", v);
}
Some(v) = rx2.recv() => {
println!("Got {:?} from rx2", v);
}
else => {
println!("Both channels closed");
}
}
}
```
在这个例子中,`select!`表达式等待从`rx1`和`rx2`接收值. 如果一个channel关闭了,`recv()`返回了`None`. 这与模式不匹配且分支会被禁用.
`select!`表达将继续在其它分支上等待.
注意`select!`表达式包含了一个`else`分支. `select!`表达式必须返回一个值. 在使用模式匹配时,可能所有的分支都不能匹配上关联的模式. 如果这种
情况发生了,那么`else`分支将会被返回.
## 借用(Borrowing)
当产生一个任务时,生成的异步表达式必须要有其所有的数据. `select!`宏没有这样的限制. 每一个分支的数据都能借用数据并同时进行操作. 根据Rust的
借用规则来看,多个异步表达式可以,**不可变**的借用单个数据,或者单个异步表达式可以**可变**的借用数据.
让我们来看一些例子. 在这里,我们同时将相同的数据发送到两个不同的TCP目标上.
```rust
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use std::io;
use std::net::SocketAddr;
async fn race(
data: &[u8],
addr1: SocketAddr,
addr2: SocketAddr
) -> io::Result<()> {
tokio::select! {
Ok(_) = async {
let mut socket = TcpStream::connect(addr1).await?;
socket.write_all(data).await?;
Ok::<_, io::Error>(())
} => {}
Ok(_) = async {
let mut socket = TcpStream::connect(addr2).await?;
socket.write_all(data).await?;
Ok::<_, io::Error>(())
} => {}
else => {}
};
Ok(())
}
```
这两个异步表达式中都是**不可变**的借用了`data`变量. 当其中一个操作成功完成后,另外一个将被丢弃. 因为我们在`Ok()`上进行了模式匹配,如果一个表达式
失败,另外一个将继续执行.
当涉及到每个分支的`<handler>`时,`select!`保证只有一个`<handler>`运行. 根据这一点,每一个`<handler>`可以**可变**的借用同一个数据.
例如,修改下两个handlers:
```rust
use tokio::sync::oneshot;
#[tokio::main]
async fn main() {
let (tx1, rx1) = oneshot::channel();
let (tx2, rx2) = oneshot::channel();
let mut out = String::new();
tokio::spawn(async move {
// 在 tx1和tx2上发送值
});
tokio::select! {
_ = rx1 => {
out.push_str("rx1 completed");
}
_ = rx2 => {
out.push_str("rx2 completed");
}
}
println!("{}", out);
}
```
## 循环(Loops)
`select!`宏经常在循环中使用. 本节将介绍一些示例,来展示在循环中使用`select!`的常用方法. 我们首先使用 multiple channels:
```rust
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
let (tx1, mut rx1) = mpsc::channel(128);
let (tx2, mut rx2) = mpsc::channel(128);
let (tx3, mut rx3) = mpsc::channel(128);
loop {
let msg = tokio::select! {
Some(msg) = rx1.recv() => msg,
Some(msg) = rx2.recv() => msg,
Some(msg) = rx3.recv() => msg,
else => { break }
};
println!("Got {}", msg);
}
println!("All channels have been closed.");
}
```
上面的示例选择了3个channel的接收器(receiver). 在任何通道上接收消息时,它将被写入到STDOUT. 当一个channel关闭时,`recv()`会返回`None`.
通过使用模式匹配,`select!`宏会继续在其它channel上等待. 当所有的通道都关闭了,`else`分支会被匹配且循环被终止.
`select!`宏会随机的选择分支来首先枪柄就绪情况. 当多个通道都有待定的值时,将从其中随机选择一个来接收. 这是为了处理接收循环处理消息的速度慢于将消息
推送到通道中的情况,这意味着通道填充数据. 如果`select!`没有随机的选择首先要检查的分支,那么在每次循环迭代中,将首先检查`rx1`. 如果`rx1`
始终都有新消息,则永远不会再检查其余的通道了.
```markdown
如果当`select!`被评估时,多个通道都有待处理的消息,只会弹出(pop)一个通道的值. 所有其它的通道保持不变,它们的消息会保留在这些通道中,直到下一次循环迭代为止. 不会有消息丢失.
```
### 恢复异步操作(Resuming an async operation)
现在,我们将展示如何在多个`select!`调用之间运行异步操作! 在这个示例当中,我们使用一个类型为`i32`的 MPSC channel,并且它是异步的. 我们要运行异步函数,直到它完成或在接收到偶数整数为止.
```rus
async fn action() {
// 一些异步逻辑
}
#[tokio::main]
async fn main() {
let (mut tx, mut rx) = tokio::sync::mpsc::channel(128);
let operation = action();
tokio::pin!(operation);
loop {
tokio::select! {
_ = &mut operation => break,
Some(v) = rx.recv() => {
if v % 2 == 0 {
break;
}
}
}
}
}
```
注意如何,而不是在`select!`宏中调用`action()`, 它在循环外被调用. `action()`的返回分配给`operation`,而不需要调用`.await`.然后我们在`operation`上调用
`tokio::pin!`.
在`select!`循里面,不是传递`operation`而是传递`&mut operation`. `operation`变量正在跟踪异步操作. 循环中的每一次迭代都使用相同的操作,而不是发出对`action()`的一次新的调用.
其它的`select!`分支从通道中接收消息. 如果消息是偶数,则循环完成. 否则再次开始 `select!`.
这里我们第一次使用了`tokio::pin!`. 我们暂时不去讨论pin的细节. 需要注意的是,为了`.await`一个引用,必须固定引用的值或者实现`Unpin`.
如果我们移除`tokio::pin!`这一行并再去尝试编译,我们会得到下面的错误:
```text
error[E0599]: no method named `poll` found for struct
`std::pin::Pin<&mut &mut impl std::future::Future>`
in the current scope
--> src/main.rs:16:9
|
16 | / tokio::select! {
17 | | _ = &mut operation => break,
18 | | Some(v) = rx.recv() => {
19 | | if v % 2 == 0 {
... |
22 | | }
23 | | }
| |_________^ method not found in
| `std::pin::Pin<&mut &mut impl std::future::Future>`
|
= note: the method `poll` exists but the following trait bounds
were not satisfied:
`impl std::future::Future: std::marker::Unpin`
which is required by
`&mut impl std::future::Future: std::future::Future`
```
这个错误不是很清晰,我们也没有讨论太多的关于`Future`的信息. 现在将`Future`看作必须通过一什值实现才能调用`.await`的trait. 如果在尝试对引用调用`.await`时遇到了有关没有实现`Future`的错误时,则可能需要固定住`Future`.
有关标准库中`Pin`更多的细节可以查看[Pin](https://doc.rust-lang.org/std/pin/index.html).
### 修改一个分支(Modifying a branch)
让我们看看一个稍微复杂的循环. 我们有:
1. `i32`值的通道.
2. 对`i321值执行的异步操作.
我们想实现的逻辑是:
1. 在channel上等待一个偶数.
2. 使用偶数作为输入启动异步操作.
3. 等待操作,但同时在channel上监听更多的偶数.
4. 如果在现有操作完成之前接收到了新的偶数,要中止现在操作,并使用新的偶数重新开始.
```rust
async fn action(input: Option<i32>) -> Option<String> {
// 如果输None则返回None
// 也可以写成 let i = input?;`
let i = match input {
Some(input) => input,
None => return None,
};
// 这里是异步逻辑
}
#[tokio::main]
async fn main() {
let (mut tx, mut rx) = tokio::sync::mpsc::channel(128);
let mut done = false;
let operation = action(None);
tokio::pin!(operation);
tokio::spawn(async move {
let _ = tx.send(1).await;
let _ = tx.send(3).await;
let _ = tx.send(2).await;
});
loop {
tokio::select! {
res = &mut operation, if !done => {
done = true;
if let Some(v) = res {
println!("GOT = {}", v);
return;
}
}
Some(v) = rx.recv() => {
if v % 2 == 0 {
// .set 是在 Pin 上的一个方法
operation.set(action(Some(v)));
done = false;
}
}
}
}
}
```
我们使用了与之前例子类似的策略. 异步函数在循环外部被调用并分配给`operation`变量. `operation`变量被固定. 循环同时在`operation`与通道接收在选择(select).
注意到,`action()`是怎样传入一个`Option<i32>`参数的,在我们收到第一个偶整数之前,我们必须实例化一些`operation`. 我们让`action()`传入`Option`并返回`Option`.
如果传入的是`None`就返回`None`. 第一次迭代,`operation`立即完成,并显示`None`.
这个示例使用了一些新语法. 第一个分支包含`, if !done`. 这是分支的前提. 在解释其工作原理之前,让我们看一下如果省略了前提条件会发生什么.
省略`, if !done` 并运行示例会得到如下输出结果:
```text
thread 'main' panicked at '`async fn` resumed after completion', src/main.rs:1:55
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
当尝试在`operation`完成之后再去使用它,就会发生此错误. 通常,在使用`.await`时,等待的值会被消费. 在这个例子中我们在一个引用上等待.
这意味着`operation`完成之后它任然存在.
为了避免这种panic,如果`operation`完成了,我们必须注意禁用第一个分支. `done`变量用于跟踪`operation`是否完成. 一个`select!`分支可以包含
一个前提条件. `select!`在分支上等待之前该前提条件会被检查. 如果前提条件的评估结果是`false`,则禁用分支. `done`变量被初始化为`false`.
当`operation`完成后,`done`被设置为`true`. 下一次循环迭代将禁用`operation`分支. 当从channel中接收到偶数时,`operation`会被重置且
`done`再次被设置为 `false`.
## 每个任务的并发(Per-task concurrency)
`tokio::spawn` 与 `select!` 都可以运行并发异步操作. 但是用于运行并发操作的策略有所不同. `tokio::spawn` 函数传入一个异步操作并产生一个
新的任务去运行它. 任务是一个tokio运行时调度的对象. Tokio独立调度两个不同的任务. 它们可以在不同的操作系统线程上同时运行. 因此产生的任务与
产生的线程都有相同的限制: 不可借用.
`select!`宏能在同一个任务上同时运行所有分支. 因为`select!`宏上的所有分支被同一个任务执行,它们永远不会同时运行. `select!`宏的多路复用
异步操作也在单个任务上运行.
← [深入异步(Async in depth)](AsyncInDepth.md)
→ [流(Streams)](Streams.md)
================================================
FILE: doc/SharedState.md
================================================
## 共享状态(Shared state)
到目前为止, 我们有了一个能工作的键值对服务. 然后, 这里有一个主要的缺陷: 状态不能在链接之间共享. 因此我们将在这篇文章中修复这个问题.
## 策略(Strategies)
在Tokio中共享状态有两种不同的方法.
1. 使用Mutex(互斥锁)保护共享状态.
2. 产生一个任务来管理状态并使用消息传递对其进行操作.
通常来说你想使用第一种方试来处理简单的数据, 对于需要异步工作的事务(比如 I/O 原语)应该使用第二种方试. 在本章节中, 共享状态是一个 `HashMap`
并使用 `insert` 与 `get` 来操作. 这些操作都不是异步的,因此我们使用 `Mutex` .
下一章节将介绍另外一种方法.
## 添加 `bytes` 依赖(Add `bytes` dependency)
Mini-Redis包使用 `bytes` 包中的 `Bytes` 类型, 而不是使用 `Vec<u8>` . `Bytes` 的目的是为网络编程提供一个健全的字节数组结构. 它在
`Vec<u8>` 上添加的最大功能是浅克隆. 换句话说, 在 `Bytes` 的实例上调用 `clone()` 方法不会复制底层的数据. 相反 `Bytes` 实例是对一些
底层数据的引用计数. `Bytes` 类型大致与一个 `Arc<Vec<u8>>` 类似, 但还添加了一些其它功能.
为了依赖 `bytes` , 在你的 `Cargo.toml` 文件中的 `[dependencies]` 下添加如下内容:
```toml
bytes = "0.5"
```
## 初始化 `HashMap` (Initialize the `HashMap` )
`HashMap` 会在许多任务和可能的许多线程之间共享. 为了支持这一点, 它被包装在 `Arc<Mutex<_>>` 中.
首先, 为了方便,使用 `use` 声明添加如下类型的别名.
```rust
use bytes::Bytes;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
type Db = Arc<Mutex<HashMap<String, Bytes>>>;
```
然后更新 `main` 函数来初始化是 `HashMap` 并传递 `Arc` 句柄给 `process` 函数. 使用 `Arc` 可以同时从许多任务中引用 `HashMap` , 这些
`HashMap` 也可能在许多线程上运行. 在整个Tokio中, 术语 **Handle** (这里译为 **句柄**)用于引用提供对某些共享状态访问的值.
```rust
use tokio::net::TcpListener;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[tokio::main]
async fn main() {
let mut listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
println!("Listening");
let db = Arc::new(Mutex::new(HashMap::new()));
loop {
let (socket, _) = listener.accept().await.unwrap();
// Clone the handle to the hash map.
let db = db.clone();
println!("Accepted");
process(socket, db).await;
}
}
```
注意, 这里使用的是 `std::sync::Mutex` 来保护 `HashMap` 并不是 `tokio::sync::Mutex` . 一个常见的错误是在异步代码中无条件的使用
`tokio::sync::Mutex` . 异步的 mutex 是一种通过调用 `.await` 来锁定的互斥锁.
同步的mutex会等待获取这把锁时阻塞当前线程. 反过来, 将阻止其它任务的处理. 然而, 换成 `tokio::sync::Mutex` 通常无济于事, 是因为异步mutex
在内部使用同步互斥锁. 根据经验, 只要锁竞争保持在低水平且在对 `.await` 的调用中不持有锁, 则可以在异步代码中使用同步互斥锁. 另外可以考虑使用
`parking_log::Mutex` 作为 `std::sync::Mutex` 更快的替代方案.
## 更新 `process()` (Update `process()`)
`process()` 函数不再初始化一个 `HashMap` . 取而代之的是, 它将 `HashMap` 的共享句柄作为一个参数传进去. 在使用它之前还需要锁定 `HashMap` .
```rust
use tokio::net::TcpStream;
use mini_redis::{connection, Frame};
async fn process(socket: TcpStream, db: Db) {
use mini_redis::Command::{self, Get, Set};
// 通过 mini-redis 提供 connection , 用来处理解析 socket中的帧
let mut connection = connection::new(socket);
while let Some(frame) = connection.read_frame().await.unwrap() {
let response = match Command::from_frame(frame).unwrap() {
Set(cmd) => {
let mut db = db.lock().unwrap();
db.insert(cmd.key().to_string(), cmd.value().clone());
Frame::Simple("OK".to_string())
}
Get(cmd) => {
let db = db.lock().unwrap();
if let Some(value) = db.get(cmd.key()) {
Frame::Bulk(value.clone())
} else {
Frame::Null
}
}
cmd => panic!("unimplemented {:?}", cmd),
};
// 写回响应到客户端
connection.write_frame(&response).await.unwrap();
}
}
```
## 任务,线程与竞争(Tasks, threads, and contention)
当竞争不激烈(很小)的时候, 使用阻塞的互斥锁来保护关键的部分是一种可接受的策略. 当去竞争锁时,执行任务的线程必须阻塞等待互斥锁. 这不仅会阻塞
当前任务, 还将阻塞当前线程上调度的所有其它任务.
默认情况下, Tokio运行时使用多线程的调度器. 任务可以被任何一个运行时管理的线程调度. 如果有大量的任务被调度去执行且它们都需要访问互斥锁,
这个时候就存在锁竞争. 反过来说, 如果使用 [current_thread](https://docs.rs/tokio/0.3/tokio/runtime/index.html#current-thread-scheduler) 则互斥锁不会被竞争.
```markdown
`current_thread` 是一个轻量级, 单线程的**运行时**, 当仅需要产生一些任务并打开少数socket时,这是一个不错的选择. 比如说, 当提供一个同步API桥接在一个异步客户端库顶部时, 此选项很好用.
```
如果在同步互斥锁上的竞争有问题时, 最好的解决办法就是少量切换到Tokio的互斥锁. 相反要考虑的选项是:
* 切换到专用任务来管理状态和使用消息传递机制.
* 分割互斥锁(译者注: 类似分段锁机制).
* 重构代码避免互斥锁.
在我们的案例中, 每一个 _key_ 都是独立的, 所以互斥锁可以很好的工作. 因此,我们将用 `N` 个不同的实例, 而不是使用单个 `Mutex<HashMap<_,_>` 实例.
```rust
type ShardedDb = Arc<Vec<Mutex<HashMap<String, Vec<u8>>>>>;
```
然后, 根据任何给定的键查找到值是两步过程. 首先, key 用来识别它是哪一部分. 然后, 在 `HashMap` 中查找key的值.
```rust
let shard = db[hash(key) % db.len()].lock().unwrap();
shard.insert(key, value);
```
(译者注: 这种分段锁思想与jdk1.8之前的ConcurrentHashMap底层实现一样).
[dashmap](https://docs.rs/dashmap) 包提供了分段hash map 实现.
## 通过 `.await` 来持有一个 `MutexGuard` (Holding a `MutexGuard` across an `.await` )
你可能写像下面这样的代码:
```rust
use std::sync::Mutex;
async fn increment_and_do_stuff(mutex: &Mutex<i32>) {
let mut lock = mutex.lock().unwrap();
*lock += 1;
do_something_async().await;
} // lock 在这里超出作用域范围
```
当你尝试生成调用此函数的内容时, 会遇到以下的错误消息:
```text
error: future cannot be sent between threads safely
--> src/lib.rs:13:5
|
13 | tokio::spawn(async move {
| ^^^^^^^^^^^^ future created by async block is not `Send`
|
::: /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.2.21/src/task/spawn.rs:127:21
|
127 | T: Future + Send + 'static,
| ---- required by this bound in `tokio::task::spawn::spawn`
|
= help: within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `std::sync::MutexGuard<'_, i32>`
note: future is not `Send` as this value is used across an await
--> src/lib.rs:7:5
|
4 | let mut lock = mutex.lock().unwrap();
| -------- has type `std::sync::MutexGuard<'_, i32>` which is not `Send`
...
7 | do_something_async().await;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here, with `mut lock` maybe used later
8 | }
| - `mut lock` is later dropped here
```
发生这的原因是, `std::sync::MutexGuard` 类型不是 `Send` . 这意味着你不能够发送一个互斥锁到另外一个线程中, 并且会发生错误, 因为Tokio
运行时可以在每个 `.await` 的线程之间移动任务. 为了避免这种情况, 你需要重组代码来使互斥锁的析构函数在 `.await` 之前运行.
```rust
use std::sync::Mutex;
// 这是可以的!
async fn increment_and_do_stuff(mutex: &Mutex<i32>) {
{
let mut lock = mutex.lock().unwrap();
*lock += 1;
}// lock 在这里超出作用域范围
do_something_async().await;
}
```
注意下面这种无法工作:
```rust
use std::sync::Mutex;
// 这也会失败
async fn increment_and_do_stuff(mutex: &Mutex<i32>) {
let mut lock = mutex.lock().unwrap();
*lock += 1;
drop(lock);
do_something_async().await;
}
```
这是因为当前编译器仅根据作用域范围信息来计算一个future是否为 `Send` . 希望将来对编译器更新后会支持显式的 drop掉它, 但目前而言, 你必须显式
的使用作用域的方式.
注意这里讨论的错误也在 [Send边界](Spawning.md) 里面有讨论.
你不应该去尝试,通过不需要一个 `Send` 的方式来产生一个任务来规避这个问题(译者注:也就是一定要使用 `Send`的方式), 是因为当任务持有锁的时候如果Tokio通过 `.await` 暂停任务, 那么可能
在同一线程上一些其它的任务可能被调度执行, 并且这些其它的任务可能也会尝试锁住互斥锁, 这将导致死锁, 因为等待锁定的互斥锁的任务将阻止持有互斥锁
的任务释放锁.
我们将在下面讨论一些解决错误消息的方法:
## 重构代码, 以免在 `.await` 中持有锁(Restructure you code to not hold the lock across an `.await` )
我们已经在上面的片段中看到了一个例子, 但这里也有更加强大的方法可能做到这一点. 比如, 你可以包装互斥锁(mutex)到一个结构体(struct)中, 且
只能将互斥锁在结构体的异步方法中锁定.
```rust
use std::sync::Mutex;
struct CanIncrement {
mutex: Mutex<i32>,
}
impl CanIncrement {
// 这个函数没有标识为异步函数
fn increment(&self) {
let mut lock = self.mutex.lock().unwrap();
*lock += 1;
}
}
async fn increment_and_do_stuff(can_incr: &CanIncrement) {
can_incr.increment();
do_something_async().await;
}
```
这种模式保证你不会遇到 `Send` 类型的错误, 是因为在异步函数的任何地方都不会出现互斥保护.
## 产生一个任务来管理状态并使用消息传递机制对其进行操作(Spawn a task to manage the state and use message passing to operate on it)
这是本章节开头提到了第二种方案, 并且当在 I/O 资源中共享资源时经常使用到. 下一章会展示更多相关的细节.
## 使用Tokio的异步互斥锁(Use Tokio's asynchronous mutex)
也可以使用 Tokio 提供的 `tokio::sync::Mutex` 类型. Tokio互斥锁主要的功能是可以在 `.await` 中持有它, 而不会产生任何问题. 也就是说,
异步互斥锁比普通互斥锁使用起来更加昂贵, 一般最好是使用其它两种方法中的一种.
```rust
use tokio::sync::Mutex; // 注意这里是使用的 tokio的 Mutex
// 这段代码是能编译的
// (但是这种情况下选择重构代码会更好)
async fn increment_and_do_stuff(mutex: &Mutex<i32>) {
let mut lock = mutex.lock().await;
*lock += 1;
do_something_async().await;
} // lock 在这里超出范围
```
← [Spawning](Spawning.md)
→ [通道](Channels.md)
================================================
FILE: doc/Spawning.md
================================================
## Spawning
我们将切换且开始在Redis Server上的工作.
首先, 将客户端的 `SET/GET` 代码从上一章节移至一个示例文件中. 这样的话我们可以在服务器上运行它.
```shell script
mkdir -p examples
mv src/main.rs examples/hello-redis.rs
```
## 接收套接字(Accepting sockets)
我们的Redis服务器需要做的第一件事是接受入站的TCP sockets. 这使用 `tokio::net::TcpListener` 来完成.
```text
大多数Tokio的类型名称被命名为和Rust标准库中具有等效功能的相同类型的名称一样. 在合理的情况个 Tokio 暴露了与 std 库相同的API, 只是tokio使用了
async fn .
```
一个 `TcpListener` 绑定到端口6379, 然后在loop循接受sockets. 每个socket 都经过处理然后关闭. 现在,我们将读取命令,将它打印到标准输出并返回错误.
```rust
use tokio::net::{TcpListener, TcpStream};
use mini_redis::{Connection, Frame};
#[tokio::main]
async fn main() {
// 绑定监听器到一个地址
let mut listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
loop {
// 第二项包含新链接的IP与端口
let (socket, _) = listener.accept().await.unwrap();
process(socket).await;
}
}
async fn process(socket: TcpStream) {
// "链接" 可以让我们通过字节流 读/写 redis的 **帧**. "链接" 类型被 mini-redis 定义.
let mut connection = Connection::new(socket);
if let Some(frame) = connection.read_frame().await.unwrap() {
println!("GOT: {:?}", frame);
// 响应一个错误
let response = Frame::Error("unimplemented".to_string());
connection.write_frame(&response).await.unwrap();
}
}
```
现在运行一个这个accept loop:
```shell script
cargo run
```
在另外一个窗口中,运行 `hello-redis` 示例(上一个章节有 `SET/GET` 命令的示例):
```shell script
cargo run --example hello-redis
```
应该会输出:
```shell script
Error: "unimplemented"
```
在服务端终端会输出:
```shell script
GOT: Array([Bulk(b"set"), Bulk(b"hello"), Bulk(b"world")])
```
## 并发(Concurrency)
我们的服务有点小问题(除了仅响应错误之外). 它一次处理一个入站请求. 当一个链接被接受后, 服务器将停在accept循环块中直到响应完成写入到socket中为止.
我们希望我们的Redis服务能处理 **更多** 的并发请求. 为了做到这一点,我们必须添加一些并发.
```text
并发与并行不是同一件事. 如果你在两个任务之间交替执行, 那么你将同时执行两个任务, 但不能并行执行.(译者注: 这种情况属于并发,不是并行)
为了使它具有并行性, 你需要两个人, 每个人专门负责每个任务.(译者注: 同时并行的执行,而不是交替).
使用tokio的优点是异步代码可以让你同时处理许多并发任务, 而不必使用普通线程并行处理它们. 事实上, Tokio可以在单个线程上并发处理许多任务.
```
为了同时处理链接,将为每一个入站的新链接产生一个新任务. 这个链接在此任务中处理.
accept 循环会变为:
```rust
use tokio::net::TcpListener;
#[tokio::main]
async fn main() {
let mut listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
loop {
let (socket, _) = listener.accept().await.unwrap();
// 为每一个入站socket链接产生一个新任务. 此socket链接被移动到这个新任务中且在里面处理.
tokio::spawn(async move {
process(socket).await;
});
}
}
```
## 任务(Tasks)
一个Tokio的任务(task)是一个异步的绿色线程. 它们通过 `async` 块 `tokio::spawn` 来创建. `tokio::spawn` 函数返回一个 `JoinHandle`
, 调用者可以使用该 `JoinHandle` 与生成的任务进行交互. `async` 块可以有一个返回值. 调用方可以在 `JoinHandle` 上使用 `.await` 获取返回值.
比如:
```rust
#[tokio::main]
async fn main() {
let handle = tokio::spawn(async {
// 做一些异步的工作
"return value"
});
// 作一些其它的工作
let out = handle.await.unwrap();
println!("GOT {}", out);
}
```
在 `JoinHandle` 等待返回一个 `Result` . 当任务在处理期间遇到一个错误时, `JoinHandle` 会返回一个 `Err`. 这种情况发生在, 当任务出现 panics 或者
任务在运行期间被关闭而强制取消时.
任务是由调度器管理的执行单元. 产生的任务会提交给Tokio的调度器, 调度器可以确保在有工作要做时执行任务. 产生的任务可以在与产生它的同一线程上执行,
也可以在不同的运行时线程上执行. 任务产生后也可以在不同的线程之间移动.
## 静态边界(`'static bound`)
通过 `tokio::spawn` 产生的任务必须是 `'static` 的. 产生(Spawned)的表达式不能借用任何数据.
有一种普遍的误解是, "静态"意味着"永久存活"("being static" means "lives forever"), 然而情况不并是这样. 如果仅仅因为值是 `'static`
的话并不意味着存在内存泄露. 有关这点你可以在[Common Rust Lifetime Misconceptions](https://github.com/pretzelhammer/rust-blog/blob/master/posts/common-rust-lifetime-misconceptions.md#2-if-t-static-then-t-must-be-valid-for-the-entire-program)
了解更多.
例如, 下面的示例将不能被编译:
```rust
use tokio::task;
#[tokio::main]
async fn main() {
let v = vec![1,2,3];
task::spawn(async {
println!("Here's a vec: {:?}", v);
});
}
```
尝试去编译会产生如下错误结果:
```text
error[E0373]: async block may outlive the current function, but
it borrows `v`, which is owned by the current function
--> src/main.rs:7:23
|
7 | task::spawn(async {
| _______________________^
8 | | println!("Here's a vec: {:?}", v);
| | - `v` is borrowed here
9 | | });
| |_____^ may outlive borrowed value `v`
|
note: function requires argument type to outlive `'static`
--> src/main.rs:7:17
|
7 | task::spawn(async {
| _________________^
8 | | println!("Here's a vector: {:?}", v);
9 | | });
| |_____^
help: to force the async block to take ownership of `v` (and any other
referenced variables), use the `move` keyword
|
7 | task::spawn(async move {
8 | println!("Here's a vec: {:?}", v);
9 | });
|
```
为发生这种情况是因为, 默认情况下, 变量是不能被移动到一个异步块中的. `v` 集合仍然被 `main` 函数所有. `println!` 行借用了 `v` .
rust的编译器能够帮助解释这一点, 甚至可以提出修改的建议! 修改第7行为 `task::spawn(async move {` 这将指示编译器将移动 `v` 到产生的任务中去.
现在任务拥有它自己的所有数据并使其为 `'static` .
如果必须同时从多个并发任务中访问单个数据, 则必须使用共享同步原语, 例如 `Arc` .
## Send 边界(`Send` bound)
通过 `tokio::spawn` 产生的任务必须实现 `Send` . 这允许Tokio运行时在任务使用 `.await` 挂起时可以在不同的线程之间移动他们.
当通过 `.await` 调用中保存的所有数据都为 `Send` 时, 任务就是一个 `Send` . 这点有些微妙. 当 `.await` 被调用时任务会返回到调度器.
下一次任务被执行会从上一次的出让点(point it last yielded)继续.(译者注: 从哪个地方出让并返回到调度器,下一次任务执行时就从那个点恢复).
若要进行这样的工作, 该任务必须保存 `.await` 之后使用的所有状态. 如果这个状态是 `Send` , 比如, 能在不同线程中移动, 则任务本身就可以跨
线程移动. 反过来, 如果状态不是 `Send` 的, 那么任务本身也就不能跨线程移动.
例如, 这种有效:
```rust
use tokio::task::yield_now;
use std::rc::Rc;
#[tokio::main]
async fn main() {
tokio::spawn(async {
// 在 .await 之前作用域强制 rc drop了
{
let rc = Rc::new("hello");
println!("{}", rc);
}
// rc 不再使用. 当任务返回到调度器后, rc 不能再持续下去
yield_now().await;
});
}
```
这一种情况却不行:
```rust
use tokio::task::yield_now;
use std::rc::Rc;
#[tokio::main]
async fn main() {
tokio::spawn(async {
let rc = Rc::new("hello");
// rc 在 .await后继续使用, 它必须持久化到 task 的 状态中才行
yield_now().await;
println!("{}", rc);
});
}
```
尝试编译上面的代码片段,会有如下结果:
```text
error: future cannot be sent between threads safely
--> src/main.rs:6:5
|
6 | tokio::spawn(async {
| ^^^^^^^^^^^^ future created by async block is not `Send`
|
::: [..]spawn.rs:127:21
|
127 | T: Future + Send + 'static,
| ---- required by this bound in
| `tokio::task::spawn::spawn`
|
= help: within `impl std::future::Future`, the trait
| `std::marker::Send` is not implemented for
| `std::rc::Rc<&str>`
note: future is not `Send` as this value is used across an await
--> src/main.rs:10:9
|
7 | let rc = Rc::new("hello");
| -- has type `std::rc::Rc<&str>` which is not `Send`
...
10 | yield_now().await;
| ^^^^^^^^^^^^^^^^^ await occurs here, with `rc` maybe
| used later
11 | println!("{}", rc);
12 | });
| - `rc` is later dropped here
```
在 [下一章](SharedState.md) 中, 我们将更深入的讨论这种错误的特殊情况.
## 存储值(Store values)
现在我们将实现一个 `process` 函数来处理传入的命令. 我们使用一个 `HashMap` 来存值. `SET` 指令将插入到 `HashMap` 中而 `GET` 指令
将它们从 `HashMap` 中加载出来. 另外, 我们将使用一个循环来接受每个链接的多个指令.
```rust
use tokio::net::TcpStream;
use mini_redis::{Connection, Frame};
async fn process(socket: TcpStream) {
use mini_redis::Command::{self, Get, Set};
use std::collections::HashMap;
// 存储数据的HashMap
let mut db = HashMap::new();
// 通过 mini-redis 提供的链接, 可以处理来自socket中的 帧
let mut connection = Connection::new(socket);
// 使用 read_frame 来接收一个来自 链接中的 命令
while let Some(frame) = connection.read_frame().await.unwrap() {
let response = match Command::from_frame(frame).unwrap() {
Set(cmd) => {
db.insert(cmd.key().to_string(), cmd.value().clone());
Frame::Simple("OK".to_string())
}
Get(cmd) => {
if let Some(value) = db.get(cmd.key()) {
Frame::Bulk(value.clone())
} else {
Frame::Null
}
}
cmd => panic!("unimplemented {:?}", cmd),
};
// 写入响应到客户端
connection.write_frame(&response).await.unwrap();
}
}
```
现在启动这个服务:
```shell script
cargo run
```
并且在另外一个窗口中运行 `hello-redis` 示例:
```shell script
cargo run --example hello-redis
```
现在得到了如下的输出:
```text
got value from the server; success=Some(b"world")
```
现在我们可以获取和设置一个值, 但是这里还有一个问题: 值不能够在链接中共享. 如果另外一个socket链接尝试通过 `GET` 得到键 `hello` 的值,
这将不会找任何东西.
你可以在 [这里](https://github.com/tokio-rs/website/blob/master/tutorial-code/spawning/src/main.rs) 找到完整的代码.
在下一章节中,我们将为所有的sockets链接实现持久化数据.
← [你好Tokio](HelloTokio.md)
→ [共享状态](SharedState.md)
================================================
FILE: doc/Streams.md
================================================
## 流(Streams)
流是一个一系列异步值的称呼. 它与Rust的 [std::iter::Iterator](https://doc.rust-lang.org/book/ch13-02-iterators.html) 异步等效且由
[Stream](https://docs.rs/tokio/0.3/tokio/stream/trait.Stream.html) trait表示. 流能在`async`函数中迭代. 它们也可以使用适配器进行
转换. Tokio在 [StreamExt](https://docs.rs/tokio/0.3/tokio/stream/trait.StreamExt.html) trait上提供了一些通用适配器.
Tokio 在 `stream` 特性标识下提供对流的支持. 当依赖Tokio时, 包括`stream`或`full`的特性能访问此功能.
```toml
tokio ={version = "0.3", features = ["stream"]}
```
我们已经看到了一些类型也实现了[Stream](https://docs.rs/tokio/0.3/tokio/stream/trait.Stream.html). 比如说,[mpsc::Receiver](https://docs.rs/tokio/0.3/tokio/sync/mpsc/struct.Receiver.html)
的接收(receive)部分也实现了`Stream`. [AsyncBufReadExt::lines()](https://docs.rs/tokio/0.3/tokio/io/trait.AsyncBufReadExt.html#method.lines)
方法采用一个被缓存的 I/O reader并返回一个 `Stream`,其中每个值代表一行数据.
## 迭代(Iteration)
当前Rust程序语言还不支持异步`for`循环. 取而代之是的使用`while let`循环与 [StreamExt::next()](https://docs.rs/tokio/0.3/tokio/stream/trait.StreamExt.html#method.next) 配对来完成流的迭代.
```rust
use tokio::stream::StreamExt;
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
let (mut tx, mut rx) = mpsc::channel(10);
tokio::spawn(async move {
tx.send(1).await.unwrap();
tx.send(2).await.unwrap();
tx.send(3).await.unwrap();
});
while let Some(v) = rx.next().await {
println!("GOT = {:?}", v);
}
}
```
像迭代器一样,`next()`方法返回`Option<T>` 其中 `T` 就是流的类型. 接收到 `None` 表明流迭代被终止了.
### Mini-Redis 广播(Mini-Redis broadcast)
让我们来看一个使用Mini-Redis客户端的更加复杂的示例.
完整的代码可以看 [这里](https://github.com/tokio-rs/website/blob/master/tutorial-code/streams/src/main.rs).
```rust
use tokio::stream::StreamExt;
use mini_redis::client;
async fn publish() -> mini_redis::Result<()> {
let mut client = client::connect("127.0.0.1:6379").await?;
// Publish some data
client.publish("numbers", "1".into()).await?;
client.publish("numbers", "two".into()).await?;
client.publish("numbers", "3".into()).await?;
client.publish("numbers", "four".into()).await?;
client.publish("numbers", "five".into()).await?;
client.publish("numbers", "6".into()).await?;
Ok(())
}
async fn subscribe() -> mini_redis::Result<()> {
let client = client::connect("127.0.0.1:6379").await?;
let subscriber = client.subscribe(vec!["numbers".to_string()]).await?;
let messages = subscriber.into_stream();
tokio::pin!(messages);
while let Some(msg) = messages.next().await {
println!("got = {:?}", msg);
}
Ok(())
}
#[tokio::main]
async fn main() -> mini_redis::Result<()> {
tokio::spawn(async {
publish().await
});
subscribe().await?;
println!("DONE");
Ok(())
}
```
产生一个任务来将消息发布到"numbers" 通道上的Mini-Redis服务上. 然后我们在main(主)任务中,订阅"numbers" 通道并显示收到的消息.
订阅之后,在返回的订阅者上调用 [into_stream()](https://docs.rs/mini-redis/0.3/mini_redis/client/struct.Subscriber.html#method.into_stream).
消息者订阅,返回在消息到达时产生消息的流. 在我们开始迭代消息之前,注意到,使用了`tokio::pin!`将流固定到堆栈. 在流上调用`next()`需要流被
[pinned](https://doc.rust-lang.org/std/pin/index.html). `into_stream()` 函数返回的流不是固定的,我们必须显示的固定住它来进行迭代.
```markdown
一个Rust的值是"pinned"时,它会被固定且它不能在内存中移动. 固定值的关键是可以将指针用作固定数据,并且调用都可以确信指针一直保持有效.
`async/await`使用此特性来支持跨`.await`点的数据**借用**.
```
如果我们忘记固定住流,我们会得到像下面这样的错误:
```text
error[E0277]: `std::future::from_generator::GenFuture<[static generator@mini_redis::client::Subscriber::into_stream::{{closure}}#0 0:mini_redis::client::Subscriber, 1:async_stream::yielder::Sender<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 'static)>>> for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6> {std::future::ResumeTy, &'r mut mini_redis::client::Subscriber, mini_redis::client::Subscriber, impl std::future::Future, (), std::result::Result<std::option::Option<mini_redis::client::Message>, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't0)>>, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't1)>, &'t2 mut async_stream::yielder::Sender<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't3)>>>, async_stream::yielder::Sender<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't4)>>>, std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't5)>>, impl std::future::Future, std::option::Option<mini_redis::client::Message>, mini_redis::client::Message}]>` cannot be unpinned
--> streams/src/main.rs:22:36
|
22 | while let Some(msg) = messages.next().await {
| ^^^^ within `impl futures_core::stream::Stream`, the trait `std::marker::Unpin` is not implemented for `std::future::from_generator::GenFuture<[static generator@mini_redis::client::Subscriber::into_stream::{{closure}}#0 0:mini_redis::client::Subscriber, 1:async_stream::yielder::Sender<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 'static)>>> for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6> {std::future::ResumeTy, &'r mut mini_redis::client::Subscriber, mini_redis::client::Subscriber, impl std::future::Future, (), std::result::Result<std::option::Option<mini_redis::client::Message>, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't0)>>, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't1)>, &'t2 mut async_stream::yielder::Sender<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't3)>>>, async_stream::yielder::Sender<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't4)>>>, std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't5)>>, impl std::future::Future, std::option::Option<mini_redis::client::Message>, mini_redis::client::Message}]>`
|
::: /home/carllerche/.cargo/registry/src/github.com-1ecc6299db9ec823/mini-redis-0.2.0/src/client.rs:398:37
|
398 | pub fn into_stream(mut self) -> impl Stream<Item = crate::Result<Message>> {
| ------------------------------------------ within this `impl futures_core::stream::Stream`
|
= note: required because it appears within the type `impl std::future::Future`
= note: required because it appears within the type `async_stream::async_stream::AsyncStream<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 'static)>>, impl std::future::Future>`
= note: required because it appears within the type `impl futures_core::stream::Stream`
error[E0277]: `std::future::from_generator::GenFuture<[static generator@mini_redis::client::Subscriber::into_stream::{{closure}}#0 0:mini_redis::client::Subscriber, 1:async_stream::yielder::Sender<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 'static)>>> for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6> {std::future::ResumeTy, &'r mut mini_redis::client::Subscriber, mini_redis::client::Subscriber, impl std::future::Future, (), std::result::Result<std::option::Option<mini_redis::client::Message>, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't0)>>, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't1)>, &'t2 mut async_stream::yielder::Sender<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't3)>>>, async_stream::yielder::Sender<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't4)>>>, std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't5)>>, impl std::future::Future, std::option::Option<mini_redis::client::Message>, mini_redis::client::Message}]>` cannot be unpinned
--> streams/src/main.rs:22:27
|
22 | while let Some(msg) = messages.next().await {
| ^^^^^^^^^^^^^^^^^^^^^ within `impl futures_core::stream::Stream`, the trait `std::marker::Unpin` is not implemented for `std::future::from_generator::GenFuture<[static generator@mini_redis::client::Subscriber::into_stream::{{closure}}#0 0:mini_redis::client::Subscriber, 1:async_stream::yielder::Sender<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 'static)>>> for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6> {std::future::ResumeTy, &'r mut mini_redis::client::Subscriber, mini_redis::client::Subscriber, impl std::future::Future, (), std::result::Result<std::option::Option<mini_redis::client::Message>, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't0)>>, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't1)>, &'t2 mut async_stream::yielder::Sender<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't3)>>>, async_stream::yielder::Sender<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't4)>>>, std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 't5)>>, impl std::future::Future, std::option::Option<mini_redis::client::Message>, mini_redis::client::Message}]>`
|
::: /home/carllerche/.cargo/registry/src/github.com-1ecc6299db9ec823/mini-redis-0.2.0/src/client.rs:398:37
|
398 | pub fn into_stream(mut self) -> impl Stream<Item = crate::Result<Message>> {
| ------------------------------------------ within this `impl futures_core::stream::Stream`
|
= note: required because it appears within the type `impl std::future::Future`
= note: required because it appears within the type `async_stream::async_stream::AsyncStream<std::result::Result<mini_redis::client::Message, std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 'static)>>, impl std::future::Future>`
= note: required because it appears within the type `impl futures_core::stream::Stream`
= note: required because of the requirements on the impl of `std::future::Future` for `tokio::stream::next::Next<'_, impl futures_core::stream::Stream>`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0277`.
error: could not compile `streams`.
To learn more, run the command again with --verbose.
```
如果你遇到一个像这样的错误,可以尝试将流固定!
在运行之前,先启动Mini-Redis 服务:
```shell script
$ mini-redis-server
```
然后尝试运行代码,我们将看到消息在标准输出流中打印.
```text
got = Ok(Message { channel: "numbers", content: b"1" })
got = Ok(Message { channel: "numbers", content: b"two" })
got = Ok(Message { channel: "numbers", content: b"3" })
got = Ok(Message { channel: "numbers", content: b"four" })
got = Ok(Message { channel: "numbers", content: b"five" })
got = Ok(Message { channel: "numbers", content: b"6" })
```
由于订阅与发布之间存在竞争,某些早期的消息可能会被丢弃. 该程序永远不会退出. 只要服务器处于活动状态,对Mini-Redis通道的订阅将保持活动状态.
让我们来看看如何使用流来扩展此程序.
## 适配器(Adapters)
接收一个`Stream`并返回一个`Stream`的函数通常被叫做"流适配器"(Stream adapters),因为他们是适配器模式中的一种形式. 公共流适配器包括
[map](https://docs.rs/tokio/0.3/tokio/stream/trait.StreamExt.html#method.map),[take](https://docs.rs/tokio/0.3/tokio/stream/trait.StreamExt.html#method.take),
和[filter](https://docs.rs/tokio/0.3/tokio/stream/trait.StreamExt.html#method.filter).
让我们更新一个Mini-Redis来让它可以退出. 在接收到三个消息之后停止迭代消息. 这可以用`take`. 此适配器限制流最多产生 `n` 个消息.
```rust
let message = subscriber.into_stream().take(3);
```
再次运行程序,我们可以看到:
```text
got = Ok(Message { channel: "numbers", content: b"1" })
got = Ok(Message { channel: "numbers", content: b"two" })
got = Ok(Message { channel: "numbers", content: b"3" })
```
这一次程序可以终止.
现在让我们将流限制为一个数字. 我们将通过消息的长度来检查. 我们使用`filter`适配器来删除与条件(译者注: predicate,这里译为条件好点)不匹配的消息.
```rust
let messages = subscriber
.into_stream()
.filter(|msg| match msg {
Ok(msg) if msg.content.len() == 1 => true,
_ => false,
})
.take(3);
```
再一次执行程序,我们看到:
```text
got = Ok(Message { channel: "numbers", content: b"1" })
got = Ok(Message { channel: "numbers", content: b"3" })
got = Ok(Message { channel: "numbers", content: b"6" })
```
请注意适配器的应用很重要. 首先调用`filter`然后再是`take`与调用`take`再`filter`是不同的.
最后我们通过剥离 `Ok(Message { ... }` 的输出部分来整理输出. 这是使用`map`来完成. 因为它应用在`filter`之后,我们知道消息是 `Ok`,所以
我们可以使用`unwrap()`.
```rust
let messages = subscriber
.into_stream()
.filter(|msg| match msg {
Ok(msg) if msg.content.len() == 1 => true,
_ => false,
})
.map(|msg| msg.unwrap().content)
.take(3);
```
现在我们得到输出:
```text
got = b"1"
got = b"3"
got = b"6"
```
另外可选的是,组合`filter`与`map`的操作步可以使用 [filter_map](https://docs.rs/tokio/0.3/tokio/stream/trait.StreamExt.html#method.filter_map).
这里有更多可用的适配器,清单请查看[这里](https://docs.rs/tokio/0.3/tokio/stream/trait.StreamExt.html).
## `Stream`的实现(Implementing `Stream`)
[Stream](https://docs.rs/tokio/0.3/tokio/stream/trait.Stream.html) trait与 [Future](https://doc.rust-lang.org/std/future/trait.Future.html) trait非常类似.
```rust
use std::pin::Pin;
use std::task::{Context, Poll};
pub trait Stream {
type Item;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>
) -> Poll<Option<Self::Item>>;
fn size_hint(&self) -> (usize, Option<usize>) {
(0, None)
}
}
```
`Stream::poll_next()` 函数与`Future::poll`非常类似,不同之处在于,它可以被重复调来从流中接收多个值. 与我们在[深入异步](AsyncInDepth.md)
中看到的一样,当流不是就绪状态时将返回`Poll::Pending`. 任务注册waker程序. 一旦应该再次轮询流时,就会通知waker.
`size_hint()` 方法使用的方式与[iterators](https://doc.rust-lang.org/book/ch13-02-iterators.html)相同.
通常当手动实现一个`Stream`时,它是通过组合future和其它流来完成的. 例如,让我们以在[深入异步](AsyncInDepth.md)中实现的`Delay` future为基础.
我们将它转换成10ms为间隔产生三次`()`的流.
```rust
use tokio::stream::Stream;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
struct Interval {
rem: usize,
delay: Delay,
}
impl Stream for Interval {
type Item = ();
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>)
-> Poll<Option<()>>
{
if self.rem == 0 {
// No more delays
return Poll::Ready(None);
}
match Pin::new(&mut self.delay).poll(cx) {
Poll::Ready(_) => {
let when = self.delay.when + Duration::from_millis(10);
self.delay = Delay { when };
self.rem -= 1;
Poll::Ready(Some(()))
}
Poll::Pending => Poll::Pending,
}
}
}
```
### `async-stream`
使用`Stream` trait手动来实现流可能很繁琐. 不幸的是,Rust语言目前还不支持在流上使用`async/await`语法. 这还在进行中,但现在还没准备好.(译者注: 指在流上的`async/await`语法)
[async-stream](https://docs.rs/async-stream) 包是一个临时可用的解决方案. 这个包提供了一个`async_stream!`的宏,可以将输入转换成一个流》
使用这个包,可以像这样实现上面的间隔需求:
```rust
use async_stream::stream;
use std::time::{Duration, Instant};
stream! {
let mut when = Instant::now();
for _ in 0..3 {
let delay = Delay { when };
delay.await;
yield ();
when += Duration::from_millis(10);
}
}
```
← [Select](Select.md)
→ [词汇表](Glossary.md)
================================================
FILE: doc/Topics.md
================================================
### 主题(Topics)
Tokio的主题页面包含了与编写异步应用时出现的各种主题相关的文章。
当前可用的主题文章有:
* [同步代码桥接 - BridgingWithSyncCode](BridgingWithSyncCode.md)
* [优雅关机 - Graceful shutdown](GracefulShutdown.md)
================================================
FILE: src/bin/channels-demo.rs
================================================
use bytes::Bytes;
use tokio::sync::{mpsc, oneshot};
use std::option::Option::Some;
use mini_redis::client;
/// 由请求者提供并通过管理任务来发送,再将命令的响应返回给请求者.
type Responder<T> = oneshot::Sender<mini_redis::Result<T>>;
#[derive(Debug)]
enum Command {
Get {
key: String,
resp: Responder<Option<Bytes>>,
},
Set {
key: String,
val: Vec<u8>,
resp: Responder<()>,
},
}
/// 示例配合 store-value 服务端使用, 或者按前面教程里 使用 mini-redis-server 服务端
#[tokio::main]
async fn main() {
let (mut tx, mut rx) = mpsc::channel(32);
// 产生一个管理任务
let manager = tokio::spawn(async move {
//建立一个与服务器的链接
let mut client = client::connect("127.0.0.1:6379").await.unwrap();
// 开始接收redis server 那边的消息
while let Some(message) = rx.recv().await {
use Command::*;
// 匹配redis返回的 命令类型
match message {
Get { key, resp } => {
let res = client.get(&key).await;
let _ = resp.send(res);
}
Set { key, val, resp } => {
let res = client.set(&key, val.into()).await;
let _ = resp.send(res);
}
}
}
});
// clone 发送者完成多个任务的发送
let mut tx2 = tx.clone();
let t1 = tokio::spawn(async move {
let (resp_tx, resp_rx) = oneshot::channel();
let cmd = Command::Get {
key: "hello".to_string(),
resp: resp_tx,
};
// 发送 GET 请求
if tx.send(cmd).await.is_err() {
eprintln!("t1 connection task shutdown");
return;
}
// 等待响应
let res = resp_rx.await;
println!("T1 GOT = {:?}", res);
});
let t2 = tokio::spawn(async move {
let (resp_tx, resp_rx) = oneshot::channel();
let cmd = Command::Set {
key: "hello".to_string(),
val: "world".into(),
resp: resp_tx,
};
if tx2.send(cmd).await.is_err() {
eprintln!("t2 connection task shutdown");
return;
}
// 等待响应
let res = resp_rx.await;
println!("T2 GOT = {:?}", res);
});
// while let Some(message) = rx.recv().await {
// println!("Got = {}", message);
// }
t1.await.unwrap();
t2.await.unwrap();
manager.await.unwrap();
}
================================================
FILE: src/bin/common/delay.rs
================================================
use std::time::Instant;
use std::future::Future;
use std::pin::Pin;
use std::task::{Poll, Context};
use std::thread;
pub struct Delay {
pub when: Instant,
}
impl Future for Delay {
type Output = &'static str;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if Instant::now() >= self.when {
println!("hello world");
Poll::Ready("done")
}else {
// 在当前任务上获取一个waker句柄
let waker = cx.waker().clone();
let when = self.when;
// 产生一个定时器线程
thread::spawn(move || {
let now = Instant::now();
if now < when {
// 说明还没到时间
thread::sleep(when - now);
}
waker.wake();
});
Poll::Pending
}
}
}
================================================
FILE: src/bin/common/mod.rs
================================================
pub mod delay;
================================================
FILE: src/bin/echo-client.rs
================================================
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
#[tokio::main]
async fn main() -> io::Result<()> {
// 链接到一个server
let tcp_stream = TcpStream::connect("127.0.0.1:6124").await?;
let (mut rd, mut wr) = io::split(tcp_stream);
let _write_task = tokio::spawn(async move {
wr.write_all(b"hello\r\n").await?;
wr.write_all(b"world\r\n").await?;
Ok::<_, io::Error>(())
});
let mut buf = vec![0; 32];
// 循环读取从服务端返回的数据
loop {
let n = rd.read(&mut buf).await?;
if n == 0 {
break;
}
println!("GOT message from server {:?}", String::from_utf8(Vec::from(&buf[..n])));
buf.clear();
}
Ok(())
}
================================================
FILE: src/bin/echo-server-copy.rs
================================================
use tokio::io;
use tokio::net::TcpListener;
#[tokio::main]
async fn main() -> io::Result<()> {
let mut listener = TcpListener::bind("127.0.0.1:6124").await.unwrap();
loop {
let (mut socket,_) = listener.accept().await?;
tokio::spawn(async move{
let (mut rd, mut wr) = socket.split();
// 打印一下
// 使用io::copy 直接将reader中的数据复制到writer中去
if io::copy(&mut rd, &mut wr).await.is_err() {
eprintln!("failed to copy");
}
});
}
}
================================================
FILE: src/bin/echo-server.rs
================================================
use tokio::io;
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> io::Result<()> {
// 绑定一个地址与端口
let mut listener = TcpListener::bind("127.0.0.1:6124").await.unwrap();
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
// 缓存buffer 手动复制内容到writer中
let mut buf:Vec<u8> =vec![0;32];
loop {
match socket.read(&mut buf).await {
// 如果是 Ok(0) 表示远程已经关闭链接, 那就直接return
Ok(0) => return,
Ok(n) => {
// 复制数据返回到socket中去, 模式匹配写回并判断是否出错了
println!("Receive data: {:?} from client", String::from_utf8(Vec::from(&buf[..n])));
if socket.write_all(&buf[..n]).await.is_err() {
// 未期待的错误,不处理直接返回
return;
}
}
Err(_) => return
}
}
});
}
}
================================================
FILE: src/bin/future-impl-demo.rs
================================================
use tokio::sync::oneshot;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
#[tokio::main]
async fn main() {
let (_tx1, rx1) = oneshot::channel();
let (_tx2, rx2) = oneshot::channel();
MySelect {
rx1,
rx2,
}.await
}
struct MySelect {
rx1: oneshot::Receiver<&'static str>,
rx2: oneshot::Receiver<&'static str>,
}
impl Future for MySelect {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Poll::Ready(val) = Pin::new(&mut self.rx1).poll(cx) {
println!("rx1 completed first with {:?}", val);
return Poll::Ready(());
}
if let Poll::Ready(val) = Pin::new(&mut self.rx2).poll(cx) {
println!("rx2 completed first with {:?}", val);
return Poll::Ready(());
}
Poll::Pending
}
}
================================================
FILE: src/bin/hello-redis-server.rs
================================================
use tokio::net::{TcpListener, TcpStream};
use mini_redis::{Connection, Frame};
#[tokio::main]
async fn main() {
// 绑定监听器到一个地址
let mut listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
// 循环接收 socket 链接
loop {
let (socket, _) = listener.accept().await.unwrap();
// process(socket).await; 如果直接处理,那么每一次只能处理一个新链接
// 将为每一个入站的新socket链接产生一个新task去处理它
tokio::spawn(async move {
process(socket).await;
});
}
}
async fn process(socket: TcpStream) {
let mut connection = Connection::new(socket);
// "链接" 可以让我们通过字节流 读/写 redis的 **帧**. "链接" 类型被 mini-redis 定义.
if let Some(frame) = connection.read_frame().await.unwrap() {
println!("GOT: {:?}", frame);
// 返回一个错误
let response = Frame::Error("unimplemented".to_string());
connection.write_frame(&response).await.unwrap();
}
}
================================================
FILE: src/bin/hello-tokio.rs
================================================
use mini_redis::{client, Result};
#[tokio::main]
pub async fn main() -> Result<()> {
// 打开一个链接到mini-redis地址的链接.
let server_address:&str = "127.0.0.1:6379";
let mut client = client::connect(server_address).await?;
// 设置 hello 的值为 world
client.set("hello", "world".into()).await?;
// 获取 hello 的值
let result = client.get("hello").await?;
println!("got value from server; result={:?}", result);
Ok(())
}
================================================
FILE: src/bin/io-demo.rs
================================================
use tokio::fs::File;
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() {
//read_to_end().await.unwrap();
//write().await.unwrap();
//write_to_all().await.unwrap();
async_copy().await.unwrap();
}
/// 异步读read()
#[allow(dead_code)]
async fn read() -> io::Result<()>{
let mut file = File::open("d:/foo.txt").await?;
// 声明一个buffer
let mut buffer = [0;20];
let n = file.read(&mut buffer).await?;
println!("The bytes : {:?}", n);
println!("The buffer content: {:?}", String::from_utf8(Vec::from(buffer)));
Ok(())
}
/// 异步读取整个文件
#[allow(dead_code)]
async fn read_to_end() -> io::Result<()>{
let mut f = File::open("d:/foo.txt").await?;
let mut buffer = Vec::new();
// 读取整个文件
f.read_to_end(&mut buffer).await?;
println!("file full content: {:?}", String::from_utf8(buffer));
Ok(())
}
/// 读取文件中的所有内容到一个buffer中去
#[allow(dead_code)]
async fn read_to_all(buffer: &mut Vec<u8>) {
let mut f = File::open("d:/foo.txt").await.unwrap();
f.read_to_end(buffer).await.unwrap();
}
#[allow(dead_code)]
async fn write() -> io::Result<()> {
let mut file = File::create("d:/create.txt").await?;
// 写入一些字符串
let n = file.write(b"some chars").await?;
println!("write the first {} bytes of 'some bytes", n);
Ok(())
}
#[allow(dead_code)]
async fn write_to_all() -> io::Result<()> {
let mut file = File::create("d:/create1.txt").await?;
// 从foo.txt文件中读取内容并写入到create1.txt中去
let mut buffer:Vec<u8> = Vec::new();
read_to_all(&mut buffer).await;
//再将buffer中的数据写到新文件中去
file.write_all(buffer.as_slice()).await?;
Ok(())
}
#[allow(dead_code)]
async fn async_copy() -> io::Result<()> {
// 实现将foo.txt reader中的内容不通过buffer直接copy到writer中去
let mut target_file = File::create("d:/foo_copy.txt").await?; // 创建目标文件, 它也就是一个writer
// 源文件
let mut src_file = File::open("d:/foo.txt").await?;
// tokio::io::copy 可以异步的将reader中的内容copy到writer中去
io::copy(&mut src_file, &mut target_file).await?;
Ok(())
}
================================================
FILE: src/bin/mini-tokio-one.rs
================================================
use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::task::Context;
use futures::task;
use std::option::Option::Some;
use std::time::{Instant, Duration};
mod common;
use common::delay::Delay;
/// mini-tokio 第一版
fn main() {
let mut mini_tokio = MiniTokio::new();
mini_tokio.spawn(async {
let when = Instant::now() + Duration::from_millis(10);
let future = Delay { when };
let out = future.await;
println!("out result: {}", out);
});
mini_tokio.run(); // 如果没执行这一句,将不会有任何的结果
}
struct MiniTokio {
tasks: VecDeque<Task>,
}
type Task = Pin<Box<dyn Future<Output=()> + Send>>;
impl MiniTokio {
// 初始化一个MiniTokio 对象
fn new() -> Self {
MiniTokio { tasks: VecDeque::new() }
}
// 在mini-tokio实例上产生一个future
fn spawn<F>(&mut self, future: F)
where
F: Future<Output=()> + Send + 'static,
{
self.tasks.push_back(Box::pin(future));
}
fn run(&mut self) {
// 创建一个新waker
let waker = task::noop_waker();
let mut context = Context::from_waker(&waker);
// 循环队列中拿任务task 并匹配,
while let Some(mut task) = self.tasks.pop_front() {
if task.as_mut().poll(&mut context).is_pending() {
self.tasks.push_back(task);
}
}
}
}
================================================
FILE: src/bin/mini-tokio-two.rs
================================================
use crossbeam::channel;
use std::sync::{Arc, Mutex};
use std::pin::Pin;
use futures::{task, task::ArcWake, Future};
use std::task::{Context};
use std::time::{Instant, Duration};
mod common;
use common::delay::Delay;
fn main() {
let mut mini_tokio = MiniTokio::new();
mini_tokio.spawn(async {
println!("这一句先打印出来!");
let when = Instant::now() + Duration::from_millis(10);
let future = Delay { when };
let out = future.await;
println!("out result: {}", out);
});
mini_tokio.run();
}
struct Task {
future: Mutex<Pin<Box<dyn Future<Output=()> + Send>>>,
executor: channel::Sender<Arc<Task>>,
}
impl Task {
fn schedule(self: &Arc<Self>) {
let _ = self.executor.send(self.clone());
}
fn poll(self: Arc<Self>) {
// 从task实例上创建一个waker. 它使用 ArcWake
let waker = task::waker(self.clone());
let mut context = Context::from_waker(&waker);
// 没有其它线程试图锁住 future,所以可以try_lock()
let mut future = self.future.try_lock().unwrap();
// 轮询future
let _ = future.as_mut().poll(&mut context);
}
/// 使用指定的future产生一个新的任务
///
/// 初始化一个新的task,它包含了future,完成后将task 推送到队列中, channel的另外一半receiver将接收到它们.
fn spawn<F>(future: F, sender: &channel::Sender<Arc<Task>>)
where F: Future<Output=()> + Send + 'static,
{
let task = Arc::new(Task {
future: Mutex::new(Box::pin(future)),
executor: sender.clone(),
});
let _ = sender.send(task);
}
}
impl ArcWake for Task {
fn wake_by_ref(arc_self: &Arc<Self>) {
arc_self.schedule();
}
}
struct MiniTokio {
scheduled: channel::Receiver<Arc<Task>>,
sender: channel::Sender<Arc<Task>>,
}
impl MiniTokio {
// 此run方法,将会一直执行
fn run(&self) {
while let Ok(task) = self.scheduled.recv() {
task.poll();
}
}
fn new() -> Self {
let (sender, scheduled) = channel::bounded(100);
MiniTokio { sender, scheduled }
}
/// MiniTokio 产生一个future
fn spawn<F>(&mut self, future: F)
where F: Future<Output=()> + Send + 'static
{
Task::spawn(future, &self.sender)
}
}
================================================
FILE: src/bin/mini-tokio.rs
================================================
//! 演示了如何实现一个非常基础的异步Rust执行器与计时器.
use std::pin::Pin;
use std::time::{Instant, Duration};
use std::future::Future;
use std::task::{Context, Poll, Waker};
use std::option::Option::Some;
use std::result::Result::Ok;
use std::sync::{Arc, Mutex};
use std::cell::RefCell;
use std::thread;
// 使用一个channel队列来调度tasks, 之所以不用std中的channel是因为std中的channel不是 Sync的,无法在线程中共享
use crossbeam::channel;
// 允许我们不使用 “不安全” 的代码来实现一个 `sta::task::waker` 功能的 工具
use futures::task::{ArcWake, self};
// 用于跟踪当前的mini-tokio实例,来使 spawn 函数能调度产生的实例.
thread_local! {
static CURRENT: RefCell<Option<channel::Sender<Arc<Task>>>> = RefCell::new(None);
}
/// main 函数入口,创建一个mini-tokio实例,并产生一些任务(Task). 我们的mini-tokio实现仅支持产生task和设置delays
///
/// 此MiniTokio,源于官方指南中的MiniTokio 原理代码,原代码链接 [mini-tokio](https://github.com/tokio-rs/website/blob/master/tutorial-code/mini-tokio/src/main.rs)
fn main() {
// 创建一个新MiniTokio实例
let mut mini_tokio = MiniTokio::new();
// 产生一个root 根据任务,所有其它的tasks都来自于这个上下文. 直到mini_tokio.run()调用之前,不会执行任何工作.
mini_tokio.spawn(async {
// 产生一个任务
spawn(async {
// 等待一小段时间以便 world在 hello后面打印
delay(Duration::from_secs(5)).await;
println!("world")
});
// 产生第二个任务
spawn(async {
println!("hello");
});
//我们没有实现执行器的shutdown功能,因此这里强制关闭
delay(Duration::from_secs(10)).await; // 2秒后关闭吧
std::process::exit(0);
});
// 启动mini-tokio 执行器循环,调度任务并接收执行结果
mini_tokio.run();
}
/// 此spawn函数功能与tokio::spawn()一样. 当进行到mini-tokio执行器(executor)中时,
/// 'CURRENT' 本地线程(thread-local) 被设置指向执行器 channel的 Send 方. 然后,产生task需要为创建的"Task"套上
/// 一个"future" 并将其推到调度队列里面.
pub fn spawn<F>(future: F)
where F: Future<Output =()> + Send + 'static,
{
CURRENT.with(|cell| {
let borrow = cell.borrow();
let sender = borrow.as_ref().unwrap();
Task::spawn(future, sender);
});
}
/// task 包含一个future和一旦future被唤醒后所必须要的数据
struct Task {
// future使用 Mutex 来包装可以使用Task具有Sync 特性.
// 仅有一个线程可以使用future.
// 真实tokio运行时,没有使用Mutex这种排它锁,而是使用了unsafe代码. box也被避免使用了.
future: Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>,
// 当task被通知时,它被发送到队列中去. 执行器通过取出通知任务来执行它们
executor: channel::Sender<Arc<Task>>,
}
impl Task {
// 使用指定的future产生一个新的future
// 初始化一个新的包含了指定future的task,并将其它推送给 sender. channel另外一半的receiver将接收到它并执行.
fn spawn<F>(future: F, sender: &channel::Sender<Arc<Task>>)
where F: Future<Output = ()> + Send + 'static,
{
let task = Arc::new(Task {
future: Mutex::new(Box::pin(future)),
executor: sender.clone(),
});
let _ = sender.send(task);
}
// 执行调度任务. 它创建了必须的 `task::Context`上下文,此Context,包含了一个waker与task.
// 使用waker对future进行poll.
fn poll(self: Arc<Self>) {
// 从task实例上创建一个waker, 它使用了 ArcWake
let waker = task::waker(self.clone());
// 使用waker来初始化task的上下文
let mut cx = Context::from_waker(&waker);
// 这里绝不会阻塞,因为只有一个线程能锁住future
let mut future = self.future.try_lock().unwrap();
// 轮询future
let _ = future.as_mut().poll(&mut cx);
}
}
// 在标准库中使用了一个低级别的API来定义waker,此API是unsafe的,为了不写unsafe代码,这里我们使用futures包提供的
// ArcWake 来定义一个waker,它可以被 Task结构体来调度.
impl ArcWake for Task {
fn wake_by_ref(arc_self: &Arc<Self>) {
// 调度Task来执行.执行器从channel中接收Task并poll Task.
let _ = arc_self.executor.send(arc_self.clone());
}
}
/// 一个基于channel的非常基础的futures 执行器(executor). 当任务(task)被唤醒时,它们通过在channel的发送方
/// 中来排队调度. 执行器在接收方(receiver)等待并执行接收到的任务.
///
/// 当一个任务被执行时,channel的发送方(sender)传递任务的Waker.
struct MiniTokio {
// 接收调度的任务. 当一个任务被安排(或调度)时, 与之相关的future会准备好推进. 这通常发生在资源任务准备执行操作的时候
// 比如说 一个socket接收到数据且一个 read 将调用成功时.
scheduled: channel::Receiver<Arc<Task>>,
sender: channel::Sender<Arc<Task>>,
}
impl MiniTokio {
// 初始化一个新的mini-tokio实例
fn new() -> MiniTokio {
let (sender, scheduled) = channel::bounded(1000);
MiniTokio{scheduled, sender}
}
// 在mini-tokio实例上产生一个future
// 给future 包装task 并将其推送到 scheduled 队列中去,当run方法被调用时future将会执行
fn spawn<F>(&mut self, future: F)
where F:Future<Output = ()> + Send + 'static,
{
Task::spawn(future, &self.sender)
}
/// 运行执行器
///
/// 这将启动执行器循环,并无限的运行,没有实现关机的机制
///
/// 任务从 scheduled 通道的 receiver方出来. 在channel上接收一个任务表明任务已经准备好被执行了.
/// 这发生在任务首次被创建和任务被唤醒时.
fn run(&self) {
println!("execute MiniTokio run method!");
// 设置 CURRENT 线程局部变量来指向当前执行器
// tokio 使用一个thread local变量来实现 `tokio::spawn`.
CURRENT.with(|cell|{
*cell.borrow_mut() = Some(self.sender.clone());
});
while let Ok(task) = self.scheduled.recv() {
task.poll();
}
}
}
// 异步等待,其作用相当于 thread::sleep. 尝试在当前函数上暂停指定的时间
//
// mini-tokio 通过一个计时器线程(timer thread) 来实现Delay, sleep 指定的duration后,一旦delay完成,就会
// 通知调用者. 每次调用delay都会产生一个线程. 显然这不是一个好的实施策略,没有人会将这种方法用在生产上(这里只是为了示例tokio原理)
// 真实的tokio没有使用这种策略.
async fn delay(dur: Duration) {
// delay 在这里是一种片面的future描述. 有时候,它被当作一种 "resource"(资源). 其它的资源包括,socket与channels.
// resource 可能不是按 async/await 来实现的,因为它们必须与一些操作系统细节合并. 因为这一原因,我们必须手动来实现 future
//
// 不过,最好将API公共为一个 async fn . 一个有用的方式是,手动定义私有future,然后从公共(pub)的`async fn`中使用它的API.
struct Delay {
// delay什么时候完成
when: Instant,
// 一旦delay完成后就会通知waker. waker必须能被timer线程与future访问,所以它使用Arc<Mutex<>>来包装.
waker: Option<Arc<Mutex<Waker>>>,
}
// 为Delay 实现 Future trait
impl Future for Delay {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// 首先,如果第一次future就被调用,则产生一个timer线程. 如果timer线程已经在运行了,要确保存储的waker
// 能匹配上当前task的waker.
// 看是否能匹配上当前task的waker
if let Some(waker) = &self.waker {
let mut waker = waker.lock().unwrap();
if !waker.will_wake(cx.waker()) {
*waker = cx.waker().clone();
}
}else {
// 如果不能匹配上,创建一个waker
let when = self.when;
let waker = Arc::new(Mutex::new(cx.waker().clone()));
// 赋值给当前task的waker
self.waker = Some(waker.clone());
// 每一次poll 被调用,产生一个timer线程
thread::spawn(move ||{
let now = Instant::now();
// 如果还没到时间
if now < when {
// 睡眠一下剩余的时间
thread::sleep(when - now);
}
// 如果duration时间过去后,再通过激活waker来通知调用者
let waker = waker.lock().unwrap();
waker.wake_by_ref();
});
}
// 一旦waker被存储且timer线程已经开始时,此时就要来检查delay是否已经完成了. 这通过当前时间来检查.
// 如果duration时间已过,Future就完成了,此时返回Poll::Ready()
if Instant::now() >= self.when {
// 说明delay duration时间已经过去
Poll::Ready(()) // 返回Poll::Ready()
}else {
// 时间没过返回Poll::Pending
Poll::Pending
}
}
}
// 回到delay function中, 初始化一个Delay实全
let future = Delay {
when: Instant::now() + dur,
waker: None, // 初始时并没有waker,它由poll去创建
};
// 等待duration完成
future.await;
}
================================================
FILE: src/bin/my-future.rs
================================================
mod common;
use std::time::{Instant, Duration};
use common::delay::Delay;
#[tokio::main]
async fn main() {
// when = 现在的时间戳+ 10ms
let when = Instant::now() + Duration::from_millis(10);
// 初始化一个Delay
let future = Delay{when};
println!("Before future.await call");
let out = future.await;
println!("future.await result : {}", out)
}
================================================
FILE: src/bin/not-compile.rs
================================================
use tokio::task;
#[tokio::main]
async fn main() {
let v = vec![1,2,4];
task::spawn(async move {
println!("Here's a vec: {:?}", v);
});
}
================================================
FILE: src/bin/select-borrow.rs
================================================
use tokio::sync::oneshot;
#[tokio::main]
async fn main() -> std::io::Result<()> {
let (tx1, rx1) = oneshot::channel();
let (tx2, rx2) = oneshot::channel();
let mut out = String::new();
tokio::spawn(async move {
let _ = tx1.send("hello");
});
tokio::spawn(async move {
let _ = tx2.send("world");
});
tokio::select! {
Ok(str) = rx1 => {
out.push_str(format!("rx1 completed: {}", str).as_str());
}
Ok(str) = rx2 => {
out.push_str(format!("rx2 completed: {}", str).as_str());
}
}
println!("{}", out);
Ok(())
}
================================================
FILE: src/bin/select-demo.rs
================================================
use tokio::sync::oneshot;
#[tokio::main]
async fn main() {
let (tx1, rx1) = oneshot::channel();
let (tx2, rx2) = oneshot::channel();
tokio::spawn(async {
let _ = tx1.send("one");
});
tokio::spawn(async {
let _ = tx2.send("two");
});
// 谁先完成返回谁,其它的丢弃
tokio::select! {
va1 = rx1 => {
println!("rx1 completed first with {:?}", va1);
}
va1 = rx2 => {
println!("rx2 completed first with {:?}", va1);
}
}
}
================================================
FILE: src/bin/select-syntax-demo.rs
================================================
use tokio::net::TcpStream;
use tokio::sync::oneshot;
#[tokio::main]
async fn main() {
let (sender, receiver) = oneshot::channel();
// 生产一个任务来发送消息到 oneshot中去
tokio::spawn(async move {
sender.send("one").unwrap();
});
tokio::select! {
// 这里不会被匹配上
socket = TcpStream::connect("localhost:3465") => {
println!("Socket connected {:?}", socket);
}
msg = receiver => {
println!("receiver message first: {:?}", msg);
}
}
}
================================================
FILE: src/bin/send-bound.rs
================================================
use tokio::task::yield_now;
use std::rc::Rc;
use std::sync::Arc;
#[tokio::main]
async fn main() {
tokio::spawn(async {
// 通过 {} 作用域 使用 rc 在 .await前drop掉
{
let rc = Rc::new("hello");
println!("{}", rc);
}
// rc 没有被使用了,所以当task返回到调度器时, 它不需要持续下去
// 所以这个例子没问题
yield_now().await; // 出让线程返回tokio 运行时调度器
});
// 但是如果改为下面这种就不能被编译
tokio::spawn(async {
let rc = Rc::new("hello");
// 如果改为Arc就没有问题,因为 Arc 实现了 Send + Sync + 'static
//let rc = Arc::new("hello");
yield_now().await;
// rc 在 .await之后还在使用, 它必须被保存在 task 的 状态中,这里没有保存所以不行
println!("{}", rc);
});
}
================================================
FILE: src/bin/shared-state.rs
================================================
use tokio::net::{TcpListener, TcpStream};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use bytes::Bytes;
use mini_redis::{Connection, Frame, Command};
use std::option::Option::Some;
use mini_redis::cmd::Command::{Set, Get};
type Db = Arc<Mutex<HashMap<String, Bytes>>>;
#[tokio::main]
async fn main() -> std::io::Result<()>{
// 声明一个listener 并绑定到指定地址的一个端口上
let mut listener = TcpListener::bind("127.0.0.1:6379").await?;
println!("Listening localhost and port 6379");
let db = Arc::new(Mutex::new(HashMap::new()));
loop {
let (socket, _) = listener.accept().await?;
// clone
let db = db.clone();
println!("Accepted");
// 处理socket
process(socket, db).await;
}
}
/// 处理函数
async fn process(socket: TcpStream, db: Db) {
let mut connection = Connection::new(socket);
while let Some(frame) = connection.read_frame().await.unwrap() {
let response = match Command::from_frame(frame).unwrap() {
Set(cmd) => {
let mut db = db.lock().unwrap();
db.insert(cmd.key().to_string(), cmd.value().clone());
// 返回 frame
Frame::Simple("OK".to_string())
}
Get(cmd) => {
let db = db.lock().unwrap();
if let Some(value) = db.get(cmd.key()) {
Frame::Bulk(value.clone())
}else {
Frame::Null
}
}
// 其它cmd 情况
cmd=> panic!("unimplemented Command :{:?}", cmd),
};
connection.write_frame(&response).await.unwrap();
}
}
================================================
FILE: src/bin/store-value.rs
================================================
use tokio::net::{TcpStream, TcpListener};
use mini_redis::{Connection, Frame};
async fn process(socket: TcpStream) {
use mini_redis::Command::{self, Get, Set};
use std::collections::HashMap;
// 声明一个用来存储数据的hashmap
let mut db = HashMap::new();
// 此connection 由mini_redis包提供, 可以处理socket中的 帧
let mut connection = Connection::new(socket);
while let Some(frame) = connection.read_frame().await.unwrap() {
let response = match Command::from_frame(frame).unwrap() {
Set(cmd) => {
db.insert(cmd.key().to_string(), cmd.value().clone());
Frame::Simple("OK".to_string())
}
Get(cmd) => {
if let Some(value) = db.get(cmd.key()) {
// Frame::Bulk() 里面要是一个Bytes 使用 into() 转换为 Bytes
Frame::Bulk(value.clone().into())
}else {
Frame::Null
}
}
// 其它的命令没有实现
cmd=> panic!("unimplemented {:?}", cmd),
};
// 写入响应到客户端
connection.write_frame(&response).await.unwrap();
}
}
#[tokio::main]
async fn main() {
// 绑定监听器到一个地址
let mut listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
println!("Mini Redis Server started, listen port: {}", 6379);
loop {
let (socket, _) = listener.accept().await.unwrap();
tokio::spawn(async move {
process(socket).await;
});
}
}
================================================
FILE: src/main.rs
================================================
mod tools;
mod relational;
fn main() {
println!("Welcome Tokio CN doc and demo!");
}
================================================
FILE: src/relational/connection.rs
================================================
use tokio::net::TcpStream;
use mini_redis::{Frame, Result};
use bytes::BytesMut;
use tokio::io::AsyncReadExt;
pub struct Connection {
stream: TcpStream,
buffer: BytesMut,
}
impl Connection {
pub fn new(stream: TcpStream) -> Connection {
Connection {
stream,
// 默认分配4kb容量给buffer
buffer: BytesMut::with_capacity(4 * 1024),
}
}
/// 从链接中读取一个帧,如果EOF到就返回 None
pub async fn read_frame(&mut self) -> Result<Option<Frame>>{
loop {
// 尝试从缓冲区中解析一个帧,如果buffer中有足够的数据那么帧就返回
if let Some(frame) = self.parse_frame()? {
return Ok(Some(frame));
}
// 如果没有足够的数据读取到一个帧中,那么尝试从socket中读取更多的数据
// 如果成功了,一定数量的字节被返回, 0 表时到了流的末尾了.
if 0 == self.stream.read_buf(&mut self.buffer).await? {
// 远程关闭了链接, 为了彻底关闭,读缓冲区中应该没有数据了,如果还存在数据那说明对等方在发送帧时关闭了socket
return if self.buffer.is_empty() {
Ok(None)
}else {
Err("connection reset by peer".into())
}
}
}
}
/// 写一个帧到链接中
pub async fn write_frame(&mut self, frame:&Frame) -> Result<()> {
}
fn parse_frame() -> Result<()>{
}
}
================================================
FILE: src/relational/frame_enum.rs
================================================
use bytes::Bytes;
enum Frame {
Simple(String),
Error(String),
Integer(u64),
Bulk(Bytes),
Null,
Array(Vec<Bytes>),
}
================================================
FILE: src/relational/mod.rs
================================================
pub mod connection;
pub mod frame_enum;
================================================
FILE: src/tools/mod.rs
================================================
mod tools;
================================================
FILE: src/tools/tools.rs
================================================
use rand::{SeedableRng, StdRng, Rng};
/// 根据一个种子值获取原data中的随机num个元素,data.len() 必须 > num
///
/// seed: u32 类型的种子, data: vec数组, num: 要随机的元素个数
///
/// 返回一个新的vec
pub fn get_random_by_seed(seed: u32, data: &Vec<u32>, num: usize) -> Box<Vec<u32>> {
let data_len = data.len();
if data_len < num {
panic!("num 必须小于 data集合元素的个数");
}
let mut seed_rng = StdRng::seed_from_u64(seed as u64);
// index_ck 用来检查已经出现的索引值
let mut index_ck: Vec<u32> = Vec::new();
let size = data.len();
// 存放新的集合
let mut new_list: Vec<u32> = Vec::new();
while index_ck.len() < num {
let temp: f32 = seed_rng.gen();
// 取到目标索引值
let v: u32 = (temp * size as f32) as u32;
// 如果查找vec,不存在v值则添加进new_list中
if !index_ck.binary_search(&v).is_ok() {
// index_ck 中 插入一个值
index_ck.push(v);
match data.get(v as usize) {
Some(result) => {
new_list.push(*result);
}
None => panic!("没有对应的索引")
};
}
}
Box::from(new_list)
}
#[test]
fn test_random() {
let seed: u32 = 20180521;
let ori_data: Vec<u32> = vec![100001, 100002, 100003, 100004, 100005, 100006, 100007];
let num = 3;
// 测试从一个vec中根据种子随机获取4个值
let random_by_seed = get_random_by_seed(seed, &ori_data, num);
println!("result box: {:?}", random_by_seed);
}
gitextract_jl6cffu2/
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── doc/
│ ├── AsyncInDepth.md
│ ├── BridgingWithSyncCode.md
│ ├── Channels.md
│ ├── Framing.md
│ ├── Glossary.md
│ ├── GracefulShutdown.md
│ ├── HelloTokio.md
│ ├── IO.md
│ ├── Introduction.md
│ ├── Select.md
│ ├── SharedState.md
│ ├── Spawning.md
│ ├── Streams.md
│ └── Topics.md
└── src/
├── bin/
│ ├── channels-demo.rs
│ ├── common/
│ │ ├── delay.rs
│ │ └── mod.rs
│ ├── echo-client.rs
│ ├── echo-server-copy.rs
│ ├── echo-server.rs
│ ├── future-impl-demo.rs
│ ├── hello-redis-server.rs
│ ├── hello-tokio.rs
│ ├── io-demo.rs
│ ├── mini-tokio-one.rs
│ ├── mini-tokio-two.rs
│ ├── mini-tokio.rs
│ ├── my-future.rs
│ ├── not-compile.rs
│ ├── select-borrow.rs
│ ├── select-demo.rs
│ ├── select-syntax-demo.rs
│ ├── send-bound.rs
│ ├── shared-state.rs
│ └── store-value.rs
├── main.rs
├── relational/
│ ├── connection.rs
│ ├── frame_enum.rs
│ └── mod.rs
└── tools/
├── mod.rs
└── tools.rs
SYMBOL INDEX (70 symbols across 24 files)
FILE: src/bin/channels-demo.rs
type Responder (line 7) | type Responder<T> = oneshot::Sender<mini_redis::Result<T>>;
type Command (line 10) | enum Command {
function main (line 25) | async fn main() {
FILE: src/bin/common/delay.rs
type Delay (line 7) | pub struct Delay {
type Output (line 12) | type Output = &'static str;
method poll (line 14) | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
FILE: src/bin/echo-client.rs
function main (line 5) | async fn main() -> io::Result<()> {
FILE: src/bin/echo-server-copy.rs
function main (line 5) | async fn main() -> io::Result<()> {
FILE: src/bin/echo-server.rs
function main (line 6) | async fn main() -> io::Result<()> {
FILE: src/bin/future-impl-demo.rs
function main (line 7) | async fn main() {
type MySelect (line 17) | struct MySelect {
type Output (line 24) | type Output = ();
method poll (line 26) | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Ou...
FILE: src/bin/hello-redis-server.rs
function main (line 5) | async fn main() {
function process (line 20) | async fn process(socket: TcpStream) {
FILE: src/bin/hello-tokio.rs
function main (line 4) | pub async fn main() -> Result<()> {
FILE: src/bin/io-demo.rs
function main (line 5) | async fn main() {
function read (line 14) | async fn read() -> io::Result<()>{
function read_to_end (line 29) | async fn read_to_end() -> io::Result<()>{
function read_to_all (line 43) | async fn read_to_all(buffer: &mut Vec<u8>) {
function write (line 49) | async fn write() -> io::Result<()> {
function write_to_all (line 60) | async fn write_to_all() -> io::Result<()> {
function async_copy (line 73) | async fn async_copy() -> io::Result<()> {
FILE: src/bin/mini-tokio-one.rs
function main (line 14) | fn main() {
type MiniTokio (line 29) | struct MiniTokio {
method new (line 38) | fn new() -> Self {
method spawn (line 43) | fn spawn<F>(&mut self, future: F)
method run (line 50) | fn run(&mut self) {
type Task (line 33) | type Task = Pin<Box<dyn Future<Output=()> + Send>>;
FILE: src/bin/mini-tokio-two.rs
function main (line 11) | fn main() {
type Task (line 26) | struct Task {
method schedule (line 32) | fn schedule(self: &Arc<Self>) {
method poll (line 36) | fn poll(self: Arc<Self>) {
method spawn (line 50) | fn spawn<F>(future: F, sender: &channel::Sender<Arc<Task>>)
method wake_by_ref (line 63) | fn wake_by_ref(arc_self: &Arc<Self>) {
type MiniTokio (line 68) | struct MiniTokio {
method run (line 76) | fn run(&self) {
method new (line 82) | fn new() -> Self {
method spawn (line 88) | fn spawn<F>(&mut self, future: F)
FILE: src/bin/mini-tokio.rs
function main (line 24) | fn main() {
function spawn (line 55) | pub fn spawn<F>(future: F)
type Task (line 66) | struct Task {
method spawn (line 78) | fn spawn<F>(future: F, sender: &channel::Sender<Arc<Task>>)
method poll (line 90) | fn poll(self: Arc<Self>) {
method wake_by_ref (line 107) | fn wake_by_ref(arc_self: &Arc<Self>) {
type MiniTokio (line 118) | struct MiniTokio {
method new (line 128) | fn new() -> MiniTokio {
method spawn (line 135) | fn spawn<F>(&mut self, future: F)
method run (line 147) | fn run(&self) {
function delay (line 168) | async fn delay(dur: Duration) {
FILE: src/bin/my-future.rs
function main (line 6) | async fn main() {
FILE: src/bin/not-compile.rs
function main (line 4) | async fn main() {
FILE: src/bin/select-borrow.rs
function main (line 4) | async fn main() -> std::io::Result<()> {
FILE: src/bin/select-demo.rs
function main (line 4) | async fn main() {
FILE: src/bin/select-syntax-demo.rs
function main (line 5) | async fn main() {
FILE: src/bin/send-bound.rs
function main (line 6) | async fn main() {
FILE: src/bin/shared-state.rs
type Db (line 9) | type Db = Arc<Mutex<HashMap<String, Bytes>>>;
function main (line 12) | async fn main() -> std::io::Result<()>{
function process (line 31) | async fn process(socket: TcpStream, db: Db) {
FILE: src/bin/store-value.rs
function process (line 4) | async fn process(socket: TcpStream) {
function main (line 37) | async fn main() {
FILE: src/main.rs
function main (line 4) | fn main() {
FILE: src/relational/connection.rs
type Connection (line 6) | pub struct Connection {
method new (line 12) | pub fn new(stream: TcpStream) -> Connection {
method read_frame (line 20) | pub async fn read_frame(&mut self) -> Result<Option<Frame>>{
method write_frame (line 42) | pub async fn write_frame(&mut self, frame:&Frame) -> Result<()> {
method parse_frame (line 46) | fn parse_frame() -> Result<()>{
FILE: src/relational/frame_enum.rs
type Frame (line 3) | enum Frame {
FILE: src/tools/tools.rs
function get_random_by_seed (line 9) | pub fn get_random_by_seed(seed: u32, data: &Vec<u32>, num: usize) -> Box...
function test_random (line 40) | fn test_random() {
Condensed preview — 45 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (211K chars).
[
{
"path": ".gitignore",
"chars": 30,
"preview": "/target\n.idea\nCargo.lock\n*.iml"
},
{
"path": "Cargo.toml",
"chars": 356,
"preview": "[package]\nname = \"tokio-cn-doc\"\nversion = \"0.1.0\"\nauthors = [\"dengshenglong <dslchd@@qq.com>\"]\nedition = \"2018\"\n\n# See m"
},
{
"path": "LICENSE",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2020 dslchd\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "README.md",
"chars": 1585,
"preview": "# Tokio 中文文档\n## 1.说明\nTokio 它是Rust语言的一种异步**运行时** 可以用来编写可靠,异步的Rust应用. 它有以下几个特点:\n* 快速: Tokio是零成本抽象的,可以带给你接近裸机的性能.\n* 可靠的: To"
},
{
"path": "doc/AsyncInDepth.md",
"chars": 16890,
"preview": "## 深入异步(Async in depth)\n到现在,我们完成了异步Rust和Tokio相当全面的介绍. 现在我们将更深入的研究Rust异步运行时模型. 在本教程最开始,我们暗示过Rust的异步采用了一种独特的方式. 现在我们将解释其含义"
},
{
"path": "doc/BridgingWithSyncCode.md",
"chars": 11591,
"preview": "## 使用同步代码桥接(Bridging with sync code)\n在大多数使用 `Tokio` 的示例中,我们使用 `#[tokio::main]` 来标记 `main` 函数,并使得整个工程是异步的。\n然而并不是所有项目都需要这样"
},
{
"path": "doc/Channels.md",
"chars": 8168,
"preview": "## 通道(Channels)\n现在我们已经学习了一些与Tokio相关的并发知识, 让我们将这些知识应用到客户端. 假设我们想运行两个并发的Redis命令. 我们可以为每一个命令产生\n一个任务来处理. 然后这两个命令将同时发生.\n\n首写我们"
},
{
"path": "doc/Framing.md",
"chars": 9969,
"preview": "## 帧(Framing)\n现在,我们将应用刚刚学到的的I/O知识,并以此来实现Mini-Redis的帧层. 形成帧是获取字节流并将其转换为帧流的过程. 帧是两个对等体之间传输数据的单位.\nRedis协议帧像下面这样:\n\n```rust\nu"
},
{
"path": "doc/Glossary.md",
"chars": 4594,
"preview": "## 词汇表(Glossary)\n### 异步(Asynchronous)\n在Rust的上下文中,异步代码指的是使用了`async/await`语言特性的代码,该功能允许很多任务在几个线程(甚至单个线程)上同时运行.\n\n### 并发与并行("
},
{
"path": "doc/GracefulShutdown.md",
"chars": 3848,
"preview": "## 优雅关机(Graceful Shutdown)\n这篇文章的目的是告诉你如何在异步应用中正常的关机。\n\n要实现优雅关机一般有3个部分:\n\n* 确定何时关机\n* 告诉程序每一部分关闭\n* 等待程序的其它部分关闭\n\n文章的剩余部分将介绍这三"
},
{
"path": "doc/HelloTokio.md",
"chars": 4012,
"preview": "## 你好 Tokio(Hello Tokio)\n我们将通过编写一个非常基础的Tokio应用来开始. 它可以连接到Mini-Redis服务, 并设置键 `hello` 的值为 `world` . 然后它可以读取这个键, 这将\n使用Mini-"
},
{
"path": "doc/IO.md",
"chars": 9132,
"preview": "## I/O\n\n在Tokio中的I/O操作几乎与`std`的相同, 但是(Tokio中的I/O操作是)是异步的. 这里有关于读( [AsyncRead](https://docs.rs/tokio/0.2/tokio/io/trait.As"
},
{
"path": "doc/Introduction.md",
"chars": 1522,
"preview": "## 指南(Tutorial)\n这篇教程将一步步带你了解构建 [Redis](https://redis.io/) 客户端与服务端的过程. 我们将从使用Rust异步编程的基础开始. 我们将实现一个Redis\n命令的子集并且获得关于Tokio"
},
{
"path": "doc/Select.md",
"chars": 15012,
"preview": "## Select\n到目前为止,当我们想向系统添加并发时,我们会产生一个新的任务(task). 现在我们将介绍使用Tokio来并发执行异步代码的其它方法.\n\n## `tokio::select!`\n`tokio::select!` 宏允许等"
},
{
"path": "doc/SharedState.md",
"chars": 7786,
"preview": "## 共享状态(Shared state)\n到目前为止, 我们有了一个能工作的键值对服务. 然后, 这里有一个主要的缺陷: 状态不能在链接之间共享. 因此我们将在这篇文章中修复这个问题.\n\n## 策略(Strategies)\n在Tokio中"
},
{
"path": "doc/Spawning.md",
"chars": 8516,
"preview": "## Spawning\n我们将切换且开始在Redis Server上的工作.\n\n首先, 将客户端的 `SET/GET` 代码从上一章节移至一个示例文件中. 这样的话我们可以在服务器上运行它.\n\n```shell script\nmkdir -"
},
{
"path": "doc/Streams.md",
"chars": 15674,
"preview": "## 流(Streams)\n流是一个一系列异步值的称呼. 它与Rust的 [std::iter::Iterator](https://doc.rust-lang.org/book/ch13-02-iterators.html) 异步等效且由"
},
{
"path": "doc/Topics.md",
"chars": 173,
"preview": "### 主题(Topics)\n\nTokio的主题页面包含了与编写异步应用时出现的各种主题相关的文章。\n\n当前可用的主题文章有:\n\n* [同步代码桥接 - BridgingWithSyncCode](BridgingWithSyncCode."
},
{
"path": "src/bin/channels-demo.rs",
"chars": 2379,
"preview": "use bytes::Bytes;\nuse tokio::sync::{mpsc, oneshot};\nuse std::option::Option::Some;\nuse mini_redis::client;\n\n/// 由请求者提供并通"
},
{
"path": "src/bin/common/delay.rs",
"chars": 860,
"preview": "use std::time::Instant;\nuse std::future::Future;\nuse std::pin::Pin;\nuse std::task::{Poll, Context};\nuse std::thread;\n\npu"
},
{
"path": "src/bin/common/mod.rs",
"chars": 14,
"preview": "pub mod delay;"
},
{
"path": "src/bin/echo-client.rs",
"chars": 731,
"preview": "use tokio::io::{self, AsyncReadExt, AsyncWriteExt};\nuse tokio::net::TcpStream;\n\n#[tokio::main]\nasync fn main() -> io::Re"
},
{
"path": "src/bin/echo-server-copy.rs",
"chars": 529,
"preview": "use tokio::io;\nuse tokio::net::TcpListener;\n\n#[tokio::main]\nasync fn main() -> io::Result<()> {\n let mut listener = T"
},
{
"path": "src/bin/echo-server.rs",
"chars": 1074,
"preview": "use tokio::io;\nuse tokio::net::TcpListener;\nuse tokio::io::{AsyncReadExt, AsyncWriteExt};\n\n#[tokio::main]\nasync fn main("
},
{
"path": "src/bin/future-impl-demo.rs",
"chars": 899,
"preview": "use tokio::sync::oneshot;\nuse std::future::Future;\nuse std::pin::Pin;\nuse std::task::{Context, Poll};\n\n#[tokio::main]\nas"
},
{
"path": "src/bin/hello-redis-server.rs",
"chars": 894,
"preview": "use tokio::net::{TcpListener, TcpStream};\nuse mini_redis::{Connection, Frame};\n\n#[tokio::main]\nasync fn main() {\n // "
},
{
"path": "src/bin/hello-tokio.rs",
"chars": 440,
"preview": "use mini_redis::{client, Result};\n\n#[tokio::main]\npub async fn main() -> Result<()> {\n // 打开一个链接到mini-redis地址的链接.\n "
},
{
"path": "src/bin/io-demo.rs",
"chars": 2051,
"preview": "use tokio::fs::File;\nuse tokio::io::{self, AsyncReadExt, AsyncWriteExt};\n\n#[tokio::main]\nasync fn main() {\n //read_to"
},
{
"path": "src/bin/mini-tokio-one.rs",
"chars": 1347,
"preview": "use std::collections::VecDeque;\nuse std::future::Future;\nuse std::pin::Pin;\nuse std::task::Context;\nuse futures::task;\nu"
},
{
"path": "src/bin/mini-tokio-two.rs",
"chars": 2205,
"preview": "use crossbeam::channel;\nuse std::sync::{Arc, Mutex};\nuse std::pin::Pin;\nuse futures::{task, task::ArcWake, Future};\nuse "
},
{
"path": "src/bin/mini-tokio.rs",
"chars": 7323,
"preview": "//! 演示了如何实现一个非常基础的异步Rust执行器与计时器.\nuse std::pin::Pin;\nuse std::time::{Instant, Duration};\nuse std::future::Future;\nuse std"
},
{
"path": "src/bin/my-future.rs",
"chars": 363,
"preview": "mod common;\nuse std::time::{Instant, Duration};\nuse common::delay::Delay;\n\n#[tokio::main]\nasync fn main() {\n // when "
},
{
"path": "src/bin/not-compile.rs",
"chars": 158,
"preview": "use tokio::task;\n\n#[tokio::main]\nasync fn main() {\n let v = vec![1,2,4];\n\n task::spawn(async move {\n printl"
},
{
"path": "src/bin/select-borrow.rs",
"chars": 626,
"preview": "use tokio::sync::oneshot;\n\n#[tokio::main]\nasync fn main() -> std::io::Result<()> {\n let (tx1, rx1) = oneshot::channel"
},
{
"path": "src/bin/select-demo.rs",
"chars": 514,
"preview": "use tokio::sync::oneshot;\n\n#[tokio::main]\nasync fn main() {\n let (tx1, rx1) = oneshot::channel();\n let (tx2, rx2) "
},
{
"path": "src/bin/select-syntax-demo.rs",
"chars": 513,
"preview": "use tokio::net::TcpStream;\nuse tokio::sync::oneshot;\n\n#[tokio::main]\nasync fn main() {\n let (sender, receiver) = ones"
},
{
"path": "src/bin/send-bound.rs",
"chars": 689,
"preview": "use tokio::task::yield_now;\nuse std::rc::Rc;\nuse std::sync::Arc;\n\n#[tokio::main]\nasync fn main() {\n tokio::spawn(asyn"
},
{
"path": "src/bin/shared-state.rs",
"chars": 1656,
"preview": "use tokio::net::{TcpListener, TcpStream};\nuse std::collections::HashMap;\nuse std::sync::{Arc, Mutex};\nuse bytes::Bytes;\n"
},
{
"path": "src/bin/store-value.rs",
"chars": 1478,
"preview": "use tokio::net::{TcpStream, TcpListener};\nuse mini_redis::{Connection, Frame};\n\nasync fn process(socket: TcpStream) {\n "
},
{
"path": "src/main.rs",
"chars": 89,
"preview": "mod tools;\nmod relational;\n\nfn main() {\n println!(\"Welcome Tokio CN doc and demo!\");\n}"
},
{
"path": "src/relational/connection.rs",
"chars": 1262,
"preview": "use tokio::net::TcpStream;\nuse mini_redis::{Frame, Result};\nuse bytes::BytesMut;\nuse tokio::io::AsyncReadExt;\n\npub struc"
},
{
"path": "src/relational/frame_enum.rs",
"chars": 140,
"preview": "use bytes::Bytes;\n\nenum Frame {\n Simple(String),\n Error(String),\n Integer(u64),\n Bulk(Bytes),\n Null,\n "
},
{
"path": "src/relational/mod.rs",
"chars": 39,
"preview": "pub mod connection;\npub mod frame_enum;"
},
{
"path": "src/tools/mod.rs",
"chars": 12,
"preview": "mod tools;\n\n"
},
{
"path": "src/tools/tools.rs",
"chars": 1404,
"preview": "use rand::{SeedableRng, StdRng, Rng};\n\n\n/// 根据一个种子值获取原data中的随机num个元素,data.len() 必须 > num\n///\n/// seed: u32 类型的种子, data: "
}
]
About this extraction
This page contains the full source code of the dslchd/tokio-cn-doc GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 45 files (146.1 KB), approximately 55.2k tokens, and a symbol index with 70 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.