3.2.编写 Accept 循环
这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。
原文链接:book.async.rs/
编写一个 Accept Loop
让我们来实现服务器的脚手架: 一个将TCP套接字绑定到地址并开始接受连接的循环。
首先我们需要引入这些:
# extern crate async_std;
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
模块,不过相对于标准库提供的线程(thread),任务(task)要轻量的多,单个线程(thread)能够运行多个任务(task)。- 对于套接字类型,我们使用
async_std
包里的TcpListener
,它与std::net::TcpListener
很相似,但是它不会阻塞,因为使用了异步(async)
的API。 - 在本例中,我们将会跳过所有有关错误处理的部分,为了能够传递错误,我们将使用
box
来存放一个实现了error
trait的对象,标准库中实现了From<&'_ str> for Box<dyn Error>
的将允许你使用?
操作符来处理错误
现在我们可以编写服务器的accept循环: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(()) }
1.我们使用了
async
来标记我们的accept_loop
函数,这样我们才能在这个函数内部使用.await
语法
2.TcpListener::bind
返回一个期货(future)
,我们在它的后面使用了.await
语法等待它的Result
出来,并且使用?
操作符获得一个TcpListener
,需要记住的是,我们.await
语法和?
操作符搭配使用的效果很不错,这和std::net::TcpListener
的工作方式一致,有区别的地方就是在这里多了.await
,async-std
的API本身就是以标准库API为镜像来设计的,很多地方都和标准库的用法一致
3.这行我们使用了模式匹配的写法来处理incoming
,如果你之前用过标准库写过一些服务的话,就会发现和这里有些不一致,为了更明了,让我们先看看标准库中是如何处理的:let listener: std::net::TcpListener = unimplemented!(); for stream in listener.incoming() { }
为什么这里我们并没有使用和标准库一致的
for
循环来处理呢,是因为语言本身并不支持async(异步)
的for
循环,所以我们不能够直接像标准库里的用法一样在这里使用for
来处理incoming
,因此我们需要手动处理迭代,使用模式匹配的话也很简单:while let Some(item) = iter.next().await
最后我们在main.rs
文件中再加入一个函数:// main.rs fn run() -> Result<()> { let fut = accept_loop("127.0.0.1:8080"); task::block_on(fut) }
要认识到一点,rust和其他语言并不相同,直接调用
async(异步)
函数是没有任何作用的,异步函数只是用来构建future(期货)
,future(期货)
本身是惰性的状态机,要开始在一个异步函数中逐步执行future(期货)
的状态机,应该使用.await
语法,在非异步函数中,执行future
的方法是将其交给执行器。在本例中,我们使用task::block_on
在当前线程上执行future
,并一直执行到完成为止。
Now we can write the server's accept loop:
# 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(())
}
- We mark the
accept_loop
function asasync
, which allows us to use.await
syntax inside. TcpListener::bind
call returns a future, which we.await
to extract theResult
, and then?
to get aTcpListener
.
Note how.await
and?
work nicely together.
This is exactly howstd::net::TcpListener
works, but with.await
added.
Mirroring API ofstd
is an explicit design goal ofasync_std
.- Here, we would like to iterate incoming sockets, just how one would do in
std
:
let listener: std::net::TcpListener = unimplemented!();
for stream in listener.incoming() {
}
Unfortunately this doesn't quite work with async
yet, because there's no support for async
for-loops in the language yet.
For this reason we have to implement the loop manually, by using while let Some(item) = iter.next().await
pattern.
最后,让我们来添加main
函数:
# 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(())
# }
#
// main
fn run() -> Result<()> {
let fut = accept_loop("127.0.0.1:8080");
task::block_on(fut)
}
在Rust中调用async
函数与在其他编程语言中调用的效果不同。在Rust中调用async
函数并不会执行该函数。async
函数仅仅是构造一个futures
对象,该对象并不会驱动状态机变化。在async
函数中,应该使用.await
后缀来驱动futures
状态机。在非async
函数中,需要使用executor
来执行一个futures
函数。在上面的例子中,我们使用task::block_on
来执行一个futures
对象,当前线程会阻塞直到这个futures
对象返回。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。