Merge pull request #1182 from iscas-zac/main

Update web-server.md:steam->stream, collecto->collect
pull/1196/head
Sunface 2 years ago committed by GitHub
commit 25d496a9be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -430,7 +430,7 @@ error: could not compile `hello` due to previous error
报错就解决呗,但 Rust 中的 channel 实现是 mpsc即多生产者单消费者因此我们无法通过克隆消费者的方式来修复这个错误。当然发送多条消息给多个接收者也不在考虑范畴该怎么办似乎陷入了绝境。
雪上加霜的是,就算 `receiver` 可以克隆,但是你得保证同一个时间只有一个`receiver` 能接收消息,否则一个任务可能同时被多个 `Worker` 执行,因此多个线程需要安全的共享和使用 `receiver`,等等,安全的共享?听上去 `Arc` 这个多所有权结构非常适合,互斥使用?貌似 `Mutex` 很适合,结合一下,`Arc<Mutext<T>>`,这不就是我们之前见过多次的线程安全类型吗?
雪上加霜的是,就算 `receiver` 可以克隆,但是你得保证同一个时间只有一个`receiver` 能接收消息,否则一个任务可能同时被多个 `Worker` 执行,因此多个线程需要安全的共享和使用 `receiver`,等等,安全的共享?听上去 `Arc` 这个多所有权结构非常适合,互斥使用?貌似 `Mutex` 很适合,结合一下,`Arc<Mutex<T>>`,这不就是我们之前见过多次的线程安全类型吗?
总之,`Arc` 允许多个 `Worker` 同时持有 `receiver`,而 `Mutex` 可以确保一次只有一个 `Worker` 能从 `receiver` 接收消息。

@ -41,7 +41,7 @@ fn main() {
`unwrap` 的使用是因为 `bind` 返回 `Result<T,E>`,毕竟监听是有可能报错的,例如:如果要监听 `80` 端口往往需要管理员权限;监听了同样的端口,等等。
`incoming` 会返回一个迭代器,它每一次迭代都会返回一个新的连接 `steam`(客户端发起web服务器监听接收),因此,接下来要做的就是从 `steam` 中读取数据,然后返回处理后的结果。
`incoming` 会返回一个迭代器,它每一次迭代都会返回一个新的连接 `stream`(客户端发起web服务器监听接收),因此,接下来要做的就是从 `stream` 中读取数据,然后返回处理后的结果。
细心的同学可能会注意到,代码中对 `stream` 还进行了一次 `unwrap` 处理,原因在于我们并不是在迭代一个一个连接,而是在迭代处理一个一个请求建立连接的尝试,而这种尝试可能会失败!例如,操作系统的最大连接数限制。

@ -147,7 +147,7 @@ trait Stream {
}
```
关于 `Stream` 的一个常见例子是消息通道( `futures` 包中的)的消费者 `Receiver`。每次有消息从 `Send` 端发送后,它都可以接收到一个 `Some(val)` 值, 一旦 `Send` 端关闭(drop),且消息通道中没有消息后,它会接收到一个 `None` 值。
关于 `Stream` 的一个常见例子是消息通道( `futures` 包中的)的消费者 `Receiver`。每次有消息从 `Send` 端发送后,它都可以接收到一个 `Some(val)` 值, 一旦 `Send` 端关闭( `drop` ),且消息通道中没有消息后,它会接收到一个 `None` 值。
```rust
async fn send_recv() {

@ -74,7 +74,7 @@ enum Poll<T> {
/// 之所以可以并发是因为两个Future的轮询可以交替进行一个阻塞另一个就可以立刻执行反之亦然
pub struct Join<FutureA, FutureB> {
// 结构体的每个字段都包含一个Future可以运行直到完成.
// 如果Future完成后字段会被设置为 `None`. 这样Future完成后就不会再被轮询
// 等到Future完成后字段会被设置为 `None`. 这样Future完成后就不会再被轮询
a: Option<FutureA>,
b: Option<FutureB>,
}
@ -160,7 +160,7 @@ trait Future {
}
```
首先这里多了一个 `Pin` ,关于它我们会在后面章节详细介绍,现在你只需要知道使用它可以创建一个无法被移动的 `Future` ,因为无法被移动,因此它将具有固定的内存地址,意味着我们可以存储它的指针(如果内存地址可能会变动,那存储指针地址将毫无意义!),也意味着可以实现一个自引用数据结构: `struct MyFut { a: i32, ptr_to_a: *const i32 }`。 而对于 `async/await` 来说,`Pin` 是不可或缺的关键特性。
首先这里多了一个 `Pin` ,关于它我们会在后面章节详细介绍,现在你只需要知道使用它可以创建一个无法被移动的 `Future` ,因为无法被移动,所以它将具有固定的内存地址,意味着我们可以存储它的指针(如果内存地址可能会变动,那存储指针地址将毫无意义!),也意味着可以实现一个自引用数据结构: `struct MyFut { a: i32, ptr_to_a: *const i32 }`。 而对于 `async/await` 来说,`Pin` 是不可或缺的关键特性。
其次,从 `wake: fn()` 变成了 `&mut Context<'_>` 。意味着 `wake` 函数可以携带数据了,为何要携带数据?考虑一个真实世界的场景,一个复杂应用例如 web 服务器可能有数千连接同时在线,那么同时就有数千 `Future` 在被同时管理着,如果不能携带数据,当一个 `Future` 调用 `wake` 后,执行器该如何知道是哪个 `Future` 调用了 `wake` ,然后进一步去 `poll` 对应的 `Future` ?没有办法!那之前的例子为啥就可以使用没有携带数据的 `wake` 因为足够简单,不存在歧义性。
@ -496,4 +496,4 @@ let event = io_blocker.block();
println!("Socket {:?} is now {:?}", event.id, event.signals);
```
这样,我们只需要一个执行器线程,它会接收 IO 事件并将其分发到对应的 `Waker` 中,接着后者会唤醒相关的任务,最终通过执行器 `poll` 后,任务可以顺利继续执行, 这种 IO 读取流程可以不停的循环,直到 `socket` 关闭。
这样,我们只需要一个执行器线程,它会接收 IO 事件并将其分发到对应的 `Waker` 中,接着后者会唤醒相关的任务,最终通过执行器 `poll` 后,任务可以顺利继续执行, 这种 IO 读取流程可以不停的循环,直到 `socket` 关闭。

@ -56,7 +56,7 @@
> 若大家使用 tokio那 CPU 密集的任务尤其需要用线程的方式去处理,例如使用 `spawn_blocking` 创建一个阻塞的线程去完成相应 CPU 密集任务。
>
> 至于具体的原因不仅是上文说到的那些还有一个是tokio 是协作式调度器,如果某个 CPU 密集的异步任务是通过 tokio 创建的那理论上来说该异步任务需要跟其它的异步任务交错执行最终大家都得到了执行皆大欢喜。但实际情况是CPU 密集的任务很可能会一直霸占着 CPU此时 tokio 的调度方式决定了该任务会一直被执行,这意味着,其它的异步任务无法得到执行的机会,最终这些任务都会因为得不到资源而饿死。
> 至于具体的原因不仅是上文说到的那些还有一个是tokio 是协作式调度器,如果某个 CPU 密集的异步任务是通过 tokio 创建的那理论上来说该异步任务需要跟其它的异步任务交错执行最终大家都得到了执行皆大欢喜。但实际情况是CPU 密集的任务很可能会一直霸占着 CPU此时 tokio 的调度方式决定了该任务会一直被执行,这意味着,其它的异步任务无法得到执行的机会,最终这些任务都会因为得不到资源而饿死。
>
> 而使用 `spawn_blocking` 后,会创建一个单独的 OS 线程,该线程并不会被 tokio 所调度( 被 OS 所调度 ),因此它所执行的 CPU 密集任务也不会导致 tokio 调度的那些异步任务被饿死

@ -329,7 +329,7 @@ error[E0277]: `PhantomPinned` cannot be unpinned
> 需要注意的是固定在栈上非常依赖于你写出的 `unsafe` 代码的正确性。我们知道 `&'a mut T` 可以固定的生命周期是 `'a` ,但是我们却不知道当生命周期 `'a` 结束后,该指针指向的数据是否会被移走。如果你的 `unsafe` 代码里这么实现了,那么就会违背 `Pin` 应该具有的作用!
>
> 一个常见的错误就是忘记去遮蔽(shadow )初始的变量,因为你可以 `drop``Pin` ,然后在 `&'a mut T` 结束后去移动数据:
> 一个常见的错误就是忘记去[遮蔽( shadow )](https://course.rs/basic/variable.html#%E5%8F%98%E9%87%8F%E9%81%AE%E8%94%BDshadowing)初始的变量,因为你可以 `drop``Pin` ,然后在 `&'a mut T` 结束后去移动数据:
>
> ```rust
> fn main() {

@ -102,7 +102,7 @@ async fn handle_connection(mut stream: TcpStream) {
在之前的代码中,我们使用了自己实现的简单的执行器来进行 `.await``poll` ,实际上这只是为了学习原理,**在实际项目中,需要选择一个三方的 `async` 运行时来实现相关的功能**。 具体的选择我们将在下一章节进行讲解,现在先选择 `async-std` ,该包的最大优点就是跟标准库的 API 类似,相对来说更简单易用。
#### 使用 async-std 作为异步运行时
#### 使用 `async-std` 作为异步运行时
下面的例子将演示如何使用一个异步运行时 `async-std` 来让之前的 `async fn` 函数运行起来,该运行时允许使用属性 `#[async_std::main]` 将我们的 `fn main` 函数变成 `async fn main` ,这样就可以在 `main` 函数中直接调用其它 `async` 函数,否则你得用之前章节的 `block_on` 方法来让 `main` 去阻塞等待异步函数的完成,但是这种简单粗暴的阻塞等待方式并不灵活。
@ -235,7 +235,7 @@ async fn main() {
至此,我们实现了同时使用并行(多线程)和并发( `async` )来同时处理多个请求!
## 测试 handle_connection 函数
## 测试 `handle_connection` 函数
对于测试 Web 服务器,使用集成测试往往是最简单的,但是在本例子中,将使用单元测试来测试连接处理函数的正确性。

Loading…
Cancel
Save