让我们实现一个“脚手架服务器”:将TCP套接字绑定到地址并开始接受连接的循环。
首先,让我们添加所需的导入样板:
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重新导出一些与futures和streams相关的traits。
task模块大致与 std::thread模块相对应,但task的要轻量得多。一个线程可以运行许多task。
对于socket类型,我们使用来自async_std的TcpListener,它与std::net::TcpListener类似,但它是非阻塞的,并且使用异步API。
在本例中,我们将跳过实现全面的错误处理。为了传播错误,我们将使用一个boxed的error trait对象。
你知道吗,标准库中有From<&'_ str> for Box<dyn Error>实现,它允许你使用字符串?operator?
现在我们可以编写服务器的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(())
}
我们将accept_loop函数标记为async,它允许我们在内部使用.await语法。
TcpListener::bind调用返回一个future,我们使用.await提取结果,然后呢操作符?获取一个TcpListener。
注意用.await和?合作使用。
这正是std::net::TcpListener的工作方式,但是添加了.await。std的Mirroring API是async-std的一个明确的设计目标。
在这里,我们想迭代传入的套接字,就像在标准库std中一样:
let listener: std::net::TcpListener = unimplemented!();
for stream in listener.incoming() {
}
但不幸的是,这还不能用于async,因为在该语言中还不支持async for循环。
为此,我们必须手动实现循环,方法是使用while let Some(item) = iter.next().await模式。
最后,让我们添加main:
// main
fn run() -> Result<()> {
let fut = accept_loop("127.0.0.1:8080");
task::block_on(fut)
}
在Rust中,与其他语言不同的一点,调用异步函数不会运行任何代码。
异步函数只构造未来,它们是惰性状态机。若要在异步函数中开始单步执行future状态机,应使用.await。
在非异步函数中,执行future的方法是将其交给执行器。
在本例中,我们使用task::block_on在当前线程上执行future,并一直执行到完成为止。