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
  1. prelude 重新导出了处理 future 和 stream 所需的一些 trait。
  2. task 模块大致对应 std::thread 模块,但任务要轻量得多。 单个线程可以运行许多任务。
  3. 对于套接字类型,我们使用 async_std 中的 TcpListener,它与 std::net::TcpListener 类似,但是非阻塞的,并使用 async API。
  4. 在本示例中,我们将跳过全面的错误处理。 为了传播错误,我们将使用装箱的错误 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(())
}
  1. 我们将 accept_loop 函数标记为 async,这允许我们在内部使用 .await 语法。
  2. TcpListener::bind 调用返回一个 future,我们对其 .await 以提取 Result,然后用 ? 获取 TcpListener。 注意 .await 和 ? 如何很好地配合。 这与 std::net::TcpListener 的工作方式完全相同,只是加上了 .await。 镜像 std 的 API 是 async_std 的明确设计目标。
  3. 在这里,我们希望遍历传入的套接字,就像在 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 并阻塞直到完成。

最后修改 August 23, 2026: 更新 (499855b16)