翻译进度
3
分块数量
4
参与人数

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
  1. prelude 包含了我们常用的一些跟future和stream有关的trait。
  2. task 模块对应的是std::thread模块,不过相对于标准库提供的线程(thread),任务(task)要轻量的多,单个线程(thread)能够运行多个任务(task)。
  3. 对于套接字类型,我们使用async_std包里的TcpListener,它与std::net::TcpListener很相似,但是它不会阻塞,因为使用了异步(async)的API。
  4. 在本例中,我们将会跳过所有有关错误处理的部分,为了能够传递错误,我们将使用box来存放一个实现了errortrait的对象,标准库中实现了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,并一直执行到完成为止。

Downtime 翻译于 4年前

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(())
}
  1. We mark the accept_loop function as async, which allows us to use .await syntax inside.
  2. TcpListener::bind call returns a future, which we .await to extract the Result, and then ? to get a TcpListener.
    Note how .await and ? work nicely together.
    This is exactly how std::net::TcpListener works, but with .await added.
    Mirroring API of std is an explicit design goal of async_std.
  3. 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对象返回。

Heng30 翻译于 3年前

本文章首发在 LearnKu.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

贡献者:4
讨论数量: 0
发起讨论 只看当前版本


暂无话题~