3.2 编写 Accept 循环
原文链接: https://book.async.rs/tutorial/accept_loop.html
编写 Accept 循环
让我们实现服务器的骨架:一个将 TCP 套接字绑定到地址并开始接受连接的循环。
首先,添加所需的导入样板代码:
1
2
3
4
5
6
7
| use async_std::{
prelude::*, // 1
task, // 2
net::{TcpListener, ToSocketAddrs}, // 3
};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; // 4
|
prelude 重新导出了处理 future 和 stream 所需的一些 trait。task 模块大致对应 std::thread 模块,但任务要轻量得多。
单个线程可以运行许多任务。- 对于套接字类型,我们使用
async_std 中的 TcpListener,它与 std::net::TcpListener 类似,但是非阻塞的,并使用 async API。 - 在本示例中,我们将跳过全面的错误处理。
为了传播错误,我们将使用装箱的错误 trait 对象。
你知道标准库中有
From<&'_ str> for Box<dyn Error> 实现吗?它允许你在 ? 运算符中使用字符串。
现在我们可以编写服务器的 accept 循环:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # extern crate async_std;
# use async_std::{
# net::{TcpListener, ToSocketAddrs},
# prelude::*,
# };
#
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { // 1
let listener = TcpListener::bind(addr).await?; // 2
let mut incoming = listener.incoming();
while let Some(stream) = incoming.next().await { // 3
// TODO
}
Ok(())
}
|
- 我们将
accept_loop 函数标记为 async,这允许我们在内部使用 .await 语法。 TcpListener::bind 调用返回一个 future,我们对其 .await 以提取 Result,然后用 ? 获取 TcpListener。
注意 .await 和 ? 如何很好地配合。
这与 std::net::TcpListener 的工作方式完全相同,只是加上了 .await。
镜像 std 的 API 是 async_std 的明确设计目标。- 在这里,我们希望遍历传入的套接字,就像在
std 中那样:
1
2
3
| let listener: std::net::TcpListener = unimplemented!();
for stream in listener.incoming() {
}
|
遗憾的是,这在 async 中还不能完全工作,因为语言中尚未支持 async for 循环。
因此,我们必须通过使用 while let Some(item) = iter.next().await 模式手动实现循环。
最后,让我们添加 main:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # extern crate async_std;
# use async_std::{
# net::{TcpListener, ToSocketAddrs},
# prelude::*,
# task,
# };
#
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#
# async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { // 1
# let listener = TcpListener::bind(addr).await?; // 2
# let mut incoming = listener.incoming();
# while let Some(stream) = incoming.next().await { // 3
# // TODO
# }
# Ok(())
# }
#
// 主函数
fn run() -> Result<()> {
let fut = accept_loop("127.0.0.1:8080");
task::block_on(fut)
}
|
需要理解的关键点是,在 Rust 中,与其他语言不同,调用 async 函数不会运行任何代码。
Async 函数只构造 future,它们是惰性的状态机。
要开始在 async 函数中步进 future 状态机,你应该使用 .await。
在非 async 函数中,执行 future 的一种方法是将其交给执行器。
在本例中,我们使用 task::block_on 在当前线程上执行 future 并阻塞直到完成。