Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • 搜索未来:SEO与GEO双引擎实战手册
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • Rust 开发入门
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • 搜索未来:SEO与GEO双引擎实战手册
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • Rust 开发入门
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain

11.3 网络编程:使用tokio与async-std

网络编程是现代软件开发的核心组成部分,Rust 通过其强大的异步编程能力,为构建高性能网络应用提供了坚实的基础。本节将深入探讨 Rust 生态中两个最流行的异步运行时:tokio 和 async-std,帮助你理解它们的核心概念、使用方法以及适用场景。

11.3.1 异步编程基础

在深入具体的库之前,我们需要回顾一下 Rust 异步编程的基础。Rust 的异步模型基于 Future trait。一个 Future 代表一个可能尚未完成的计算。异步函数(async fn)返回一个实现了 Future 的类型,但它本身不会立即执行,需要由一个异步运行时来驱动执行。

异步运行时的核心职责是:

  1. 调度任务:管理并发执行的任务(Task)。
  2. 管理 I/O 事件:监听文件描述符(如 socket)上的事件(如可读、可写),并在事件就绪时唤醒相应的任务。
  3. 提供非阻塞 I/O 原语:提供 TcpListener、TcpStream、UdpSocket 等网络类型的异步版本。

tokio 和 async-std 都是这样的运行时,它们提供了类似的功能,但在设计哲学和 API 细节上有所不同。

11.3.2 使用 tokio 进行网络编程

tokio 是 Rust 生态中最成熟、使用最广泛的异步运行时。它以其高性能、丰富的功能和强大的生态系统而闻名。

1. 添加依赖

在你的 Cargo.toml 中添加 tokio 依赖。通常我们会启用 full 特性来获得所有功能,包括网络、文件 I/O、时间等。

[dependencies]
tokio = { version = "1", features = ["full"] }

2. 一个简单的 TCP 服务器

以下是一个使用 tokio 创建的简单 TCP 回显服务器(Echo Server)。它监听 127.0.0.1:8080,并将接收到的任何数据原样发送回客户端。

use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 绑定到本地地址和端口
    let listener = TcpListener::bind("127.0.0.1:8080").await?;
    println!("Server listening on 127.0.0.1:8080");

    // 循环接受新的连接
    loop {
        // 等待一个新连接
        let (mut socket, addr) = listener.accept().await?;
        println!("New connection from: {}", addr);

        // 为每个连接创建一个新的异步任务
        tokio::spawn(async move {
            // 创建一个缓冲区来存储读取的数据
            let mut buf = [0; 1024];

            // 循环读取和写入数据
            loop {
                // 从 socket 读取数据
                let n = match socket.read(&mut buf).await {
                    // 返回 0 表示连接已关闭
                    Ok(0) => {
                        println!("Connection closed by client: {}", addr);
                        return;
                    }
                    Ok(n) => n,
                    Err(e) => {
                        eprintln!("Failed to read from socket: {}", e);
                        return;
                    }
                };

                // 将读取到的数据原样写回 socket
                if let Err(e) = socket.write_all(&buf[..n]).await {
                    eprintln!("Failed to write to socket: {}", e);
                    return;
                }
            }
        });
    }
}

代码解读:

  • #[tokio::main]: 这是一个宏,它将 main 函数标记为异步入口点,并自动启动 tokio 运行时。
  • TcpListener::bind(...): 创建一个 TCP 监听器。.await 是异步等待的关键字,它会挂起当前任务直到绑定完成。
  • listener.accept(): 异步等待一个新连接。当有客户端连接时,它返回一个 TcpStream 和客户端的地址。
  • tokio::spawn(...): 这是创建并发任务的核心方法。它会在 tokio 运行时中启动一个新的异步任务。这允许服务器同时处理多个客户端连接,而无需为每个连接创建一个操作系统线程。
  • AsyncReadExt 和 AsyncWriteExt: 这些 trait 为 TcpStream 提供了异步的 read 和 write_all 方法。

3. 一个简单的 TCP 客户端

use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 连接到服务器
    let mut stream = TcpStream::connect("127.0.0.1:8080").await?;
    println!("Connected to server");

    // 发送数据
    let msg = b"Hello, tokio!";
    stream.write_all(msg).await?;
    println!("Sent: {}", String::from_utf8_lossy(msg));

    // 读取响应
    let mut buf = [0; 1024];
    let n = stream.read(&mut buf).await?;
    println!("Received: {}", String::from_utf8_lossy(&buf[..n]));

    Ok(())
}

11.3.3 使用 async-std 进行网络编程

async-std 是另一个流行的异步运行时,其设计哲学是提供与标准库(std)几乎相同的 API,但全部是异步的。这使得从同步代码迁移到异步代码更加直观。

1. 添加依赖

[dependencies]
async-std = "1.12"

2. 一个简单的 TCP 服务器

使用 async-std 实现相同的回显服务器:

use async_std::io::{ReadExt, WriteExt};
use async_std::net::TcpListener;
use async_std::task;

#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let listener = TcpListener::bind("127.0.0.1:8081").await?;
    println!("Server listening on 127.0.0.1:8081");

    loop {
        let (mut socket, addr) = listener.accept().await?;
        println!("New connection from: {}", addr);

        // 使用 task::spawn 创建新任务
        task::spawn(async move {
            let mut buf = [0; 1024];
            loop {
                let n = match socket.read(&mut buf).await {
                    Ok(0) => {
                        println!("Connection closed by client: {}", addr);
                        return;
                    }
                    Ok(n) => n,
                    Err(e) => {
                        eprintln!("Failed to read from socket: {}", e);
                        return;
                    }
                };

                if let Err(e) = socket.write_all(&buf[..n]).await {
                    eprintln!("Failed to write to socket: {}", e);
                    return;
                }
            }
        });
    }
}

3. 一个简单的 TCP 客户端

use async_std::io::{ReadExt, WriteExt};
use async_std::net::TcpStream;

#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut stream = TcpStream::connect("127.0.0.1:8081").await?;
    println!("Connected to server");

    let msg = b"Hello, async-std!";
    stream.write_all(msg).await?;
    println!("Sent: {}", String::from_utf8_lossy(msg));

    let mut buf = [0; 1024];
    let n = stream.read(&mut buf).await?;
    println!("Received: {}", String::from_utf8_lossy(&buf[..n]));

    Ok(())
}

11.3.4 tokio 与 async-std 的对比

特性tokioasync-std
成熟度与生态非常成熟,生态庞大(hyper, tonic, warp 等)较新,生态相对较小
设计哲学功能丰富,性能极致,提供更多底层控制API 简洁,与 std 高度一致,易于上手
运行时模型多线程工作窃取调度器,性能出色同样基于多线程,但调度策略略有不同
核心组件tokio::net, tokio::io, tokio::syncasync_std::net, async_std::io, async_std::sync
宏#[tokio::main], #[tokio::test]#[async_std::main], #[async_std::test]
学习曲线稍陡峭,概念较多(如 Runtime, Handle)较平缓,API 命名与标准库相似
流行框架Actix-web, Axum, Warp, Tonic (gRPC)Tide, Surf (HTTP 客户端)

选择建议:

  • 如果你追求极致的性能、需要访问最广泛的生态系统(如 gRPC、高性能 HTTP 框架),或者正在构建一个复杂的生产系统,tokio 是首选。
  • 如果你是从同步 Rust 迁移过来,希望 API 尽可能熟悉,或者你的项目规模较小、对生态依赖不大,async-std 会是一个很好的起点。

11.3.5 高级概念与最佳实践

无论你选择哪个运行时,以下概念都是通用的:

  1. 避免阻塞调用:在异步任务中,永远不要调用阻塞的同步 I/O 函数(如 std::thread::sleep、std::io::Read::read)。这会导致整个线程被阻塞,影响所有共享该线程的任务。应始终使用运行时提供的异步版本(如 tokio::time::sleep、async_std::task::sleep)。

  2. 合理使用 spawn:spawn 用于创建可以独立运行的并发任务。它适用于处理每个客户端连接、执行后台计算等场景。对于简单的、需要顺序执行的操作,直接 await 即可。

  3. 理解 Send 和 Sync:tokio::spawn 要求被 spawn 的 Future 是 Send 的。这意味着它内部的所有数据都必须在任务之间安全地移动。如果你的 Future 包含 Rc 或 RefCell,它们不是 Send 的,因此不能在 tokio::spawn 中使用。这时可以考虑使用 Arc<Mutex<T>> 或 tokio::sync 模块中的原语。

  4. 选择合适的同步原语:

    • tokio::sync::Mutex: 用于在异步上下文中保护共享数据。与 std::sync::Mutex 不同,它会在锁被持有时 .await,从而避免阻塞线程。
    • tokio::sync::RwLock: 读写锁,适用于读多写少的场景。
    • tokio::sync::mpsc: 多生产者、单消费者通道,用于任务间通信。
    • tokio::sync::oneshot: 单次通道,用于发送一个值。
  5. 使用 select! 宏:tokio::select! 允许你同时等待多个 Future,并在其中一个完成时执行相应的代码。这在处理超时、优雅关闭等场景中非常有用。

    use tokio::time::{sleep, Duration};
    use tokio::select;
    
    #[tokio::main]
    async fn main() {
        let operation = async {
            // 模拟一个长时间运行的操作
            sleep(Duration::from_secs(5)).await;
            "Operation completed"
        };
    
        let timeout = sleep(Duration::from_secs(3));
    
        select! {
            result = operation => println!("{}", result),
            _ = timeout => println!("Operation timed out"),
        }
    }
    

11.3.6 总结

本节介绍了 Rust 网络编程的两大核心异步运行时:tokio 和 async-std。通过具体的 TCP 回显服务器示例,我们学习了如何使用它们进行基本的网络 I/O 操作。理解它们的设计哲学、核心概念(如 spawn、Future、await)以及最佳实践(如避免阻塞调用、选择合适的同步原语),是构建高性能、可靠的 Rust 网络应用的关键。无论你最终选择哪个运行时,掌握这些底层原理都将使你在 Rust 异步编程的道路上更加游刃有余。

Last Updated:: 5/9/26, 3:13 PM