3.9 实现客户端

原文链接: https://book.async.rs/tutorial/implementing_a_client.html

实现客户端

由于协议是基于行的,实现聊天客户端很简单:

  • 从 stdin 读取的行应通过套接字发送。
  • 从套接字读取的行应回显到 stdout。

虽然 async 不会显著影响客户端性能(因为与服务器不同,客户端只与一个用户交互,只需要有限的并发),但 async 对于管理并发仍然很有用!

客户端必须同时从 stdin 和套接字读取。 用线程编程这很繁琐,尤其是在实现优雅关闭时。 使用 async,只需要 select! 宏。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# extern crate futures;
use async_std::{
    io::{stdin, BufReader},
    net::{TcpStream, ToSocketAddrs},
    prelude::*,
    task,
};
use futures::{select, FutureExt};

type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;

// 主函数
fn run() -> Result<()> {
    task::block_on(try_run("127.0.0.1:8080"))
}

async fn try_run(addr: impl ToSocketAddrs) -> Result<()> {
    let stream = TcpStream::connect(addr).await?;
    let (reader, mut writer) = (&stream, &stream); // 1
    let mut lines_from_server = BufReader::new(reader).lines().fuse(); // 2
    let mut lines_from_stdin = BufReader::new(stdin()).lines().fuse(); // 2
    loop {
        select! { // 3
            line = lines_from_server.next().fuse() => match line {
                Some(line) => {
                    let line = line?;
                    println!("{}", line);
                },
                None => break,
            },
            line = lines_from_stdin.next().fuse() => match line {
                Some(line) => {
                    let line = line?;
                    writer.write_all(line.as_bytes()).await?;
                    writer.write_all(b"\n").await?;
                }
                None => break,
            }
        }
    }
    Ok(())
}
  1. 这里我们将 TcpStream 拆分为读半部分和写半部分:有 impl AsyncRead for &'_ TcpStream,就像 std 中的一样。
  2. 我们为套接字和 stdin 创建行的流。
  3. 在主 select 循环中,我们打印从服务器接收的行,并发送从控制台读取的行。
最后修改 August 23, 2026: 更新 (499855b16)