Rust 异步编程实践:用 Tokio 实现一个迷你 HTTP 服务

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

    • [一、前言:为什么要学习 Rust 异步编程?](#一、前言:为什么要学习 Rust 异步编程?)
    • [二、async/.await:TypeScript 与 Rust 的差异](#二、async/.await:TypeScript 与 Rust 的差异)
    • [三、Tokio 异步模型简介](#三、Tokio 异步模型简介)
    • [四、实战:用 Tokio 构建一个迷你 HTTP 服务](#四、实战:用 Tokio 构建一个迷你 HTTP 服务)
      • [1 新建项目](#1 新建项目)
      • [2 编辑 Cargo.toml](#2 编辑 Cargo.toml)
      • [3 编写服务代码](#3 编写服务代码)
    • [五、测试:用 Reqwest 模拟前端请求](#五、测试:用 Reqwest 模拟前端请求)
    • 六、错误处理与日志记录实践
    • [七、性能对比:Rust Server vs Node Server](#七、性能对比:Rust Server vs Node Server)
    • [八、结语:Rust 异步的真正意义](#八、结语:Rust 异步的真正意义)

一、前言:为什么要学习 Rust 异步编程?

在现代 Web 开发中,"异步"几乎无处不在。

Node.js、Go、Python asyncio 乃至 Java 的 Reactor 模型,

都证明了异步是高并发服务的核心能力。

但 Rust 的异步体系与其他语言完全不同:

  • 没有 GC(垃圾回收),内存安全由编译器保证;
  • 它的异步是零成本抽象,没有隐藏的性能损耗;
  • 它通过 Future trait + Pin + Poll 实现极高效的事件驱动模型。

Tokio 是 Rust 最成熟的异步运行时,它将异步任务、调度器、网络 IO、定时器整合在一起。

简单来说:Tokio 之于 Rust,就像 Node.js runtime 之于 JavaScript。


二、async/.await:TypeScript 与 Rust 的差异

Rust 的异步语法和 TypeScript 类似,也有 asyncawait,但底层原理差异巨大。

对比项 TypeScript Rust
编译后执行模型 Promise(对象) Future(状态机)
运行时调度 V8 引擎 Tokio 运行时
错误处理 try/catch Result<T, E>
异步模型 单线程事件循环 多线程任务调度
典型语法 await fetch(url) let resp = client.get(url).await?;

在 TS 中,await 是语法糖,最终都会转化为 Promise 链。

而在 Rust 中,async 函数返回的是 impl Future

它不会自动执行,必须交由一个运行时(如 Tokio)poll 才能真正运行。

rust 复制代码
async fn say_hello() {
    println!("Hello from async Rust!");
}

#[tokio::main]
async fn main() {
    say_hello().await;
}

当你看到 .await,它实际上是在底层执行一个状态机的 Poll 操作。

这也是 Rust 异步效率极高的根本原因。


三、Tokio 异步模型简介

Tokio 提供一个高性能的多线程运行时,负责:

  1. 任务调度(Scheduler)
  2. IO 事件监听(基于 epoll/kqueue)
  3. 定时器、信号、通道(Channel)支持

其核心组件结构如下:

text 复制代码
┌──────────────────────────────────┐
│ Tokio Runtime                   │
│  ├── Executor(任务执行器)       │
│  ├── Reactor(IO事件监听)        │
│  ├── Timer(定时器)             │
│  └── Channel(任务通信)         │
└──────────────────────────────────┘

运行时会将多个异步任务分配给不同线程的执行器(Worker),

并在 IO 就绪时唤醒对应 Future 继续执行,真正实现高并发非阻塞。


四、实战:用 Tokio 构建一个迷你 HTTP 服务

我们来写一个支持 GET / POST 请求 的小型 HTTP 服务,

类似于 Express 的极简版本。


1 新建项目

bash 复制代码
cargo new tokio-http-demo
cd tokio-http-demo

2 编辑 Cargo.toml

toml 复制代码
[package]
name = "tokio-http-demo"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio = { version = "1.37", features = ["full"] }
hyper = "0.14"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

这里我们使用:

  • tokio:异步运行时;
  • hyper:HTTP 框架(底层非阻塞 IO);
  • serde + serde_json:用于解析 JSON 请求。

3 编写服务代码

src/main.rs

rust 复制代码
use hyper::{
    service::{make_service_fn, service_fn},
    Body, Request, Response, Server, Method, StatusCode,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::convert::Infallible;

#[derive(Serialize, Deserialize, Debug)]
struct User {
    name: String,
    age: u8,
}

async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
    match (req.method(), req.uri().path()) {
        // GET 接口
        (&Method::GET, "/hello") => {
            let res = json!({ "message": "Hello from Rust server!" });
            Ok(Response::new(Body::from(res.to_string())))
        }

        // POST 接口
        (&Method::POST, "/user") => {
            let whole_body = hyper::body::to_bytes(req.into_body()).await.unwrap();
            let user: User = serde_json::from_slice(&whole_body).unwrap();
            let reply = json!({ "status": "ok", "user": user });
            Ok(Response::new(Body::from(reply.to_string())))
        }

        // 其他路径
        _ => {
            let mut not_found = Response::default();
            *not_found.status_mut() = StatusCode::NOT_FOUND;
            Ok(not_found)
        }
    }
}

#[tokio::main]
async fn main() {
    let addr = ([127, 0, 0, 1], 8080).into();

    let make_svc = make_service_fn(|_conn| async {
        Ok::<_, Infallible>(service_fn(handle_request))
    });

    let server = Server::bind(&addr).serve(make_svc);

    println!("🚀 Server running at http://{}", addr);

    if let Err(e) = server.await {
        eprintln!("Server error: {}", e);
    }
}

启动服务器:

bash 复制代码
cargo run

终端输出:

复制代码
 Server running at http://127.0.0.1:8080

五、测试:用 Reqwest 模拟前端请求

我们可以直接用 Rust 的 HTTP 客户端 reqwest 来测试服务,也可以用 Postman。

新建 tests/client.rs

rust 复制代码
use reqwest::Client;
use serde_json::json;

#[tokio::test]
async fn test_http_server() {
    let client = Client::new();

    // 测试 GET
    let res = client
        .get("http://127.0.0.1:8080/hello")
        .send()
        .await
        .unwrap()
        .text()
        .await
        .unwrap();
    println!("GET /hello -> {}", res);

    // 测试 POST
    let payload = json!({ "name": "Kaze", "age": 28 });
    let res = client
        .post("http://127.0.0.1:8080/user")
        .json(&payload)
        .send()
        .await
        .unwrap()
        .text()
        .await
        .unwrap();

    println!("POST /user -> {}", res);
}

执行:

bash 复制代码
cargo test -- --nocapture

输出:

复制代码
GET /hello -> {"message":"Hello from Rust server!"}
POST /user -> {"status":"ok","user":{"name":"Kaze","age":28}}

成功 !


六、错误处理与日志记录实践

Rust 的错误模型是显式的。我们可以通过 Result + ? 运算符优雅处理错误。

并使用 tracing 进行日志管理。

Cargo.toml 添加:

toml 复制代码
tracing = "0.1"
tracing-subscriber = "0.3"

main 函数中初始化日志:

rust 复制代码
use tracing::{info, error};
use tracing_subscriber;

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();
    info!(" HTTP 服务启动中...");

    // ...
    if let Err(e) = server.await {
        error!("服务器异常: {}", e);
    }
}

运行后,日志会带有时间戳、线程号,非常清晰:

复制代码
2025-11-04T10:35:21 INFO  tokio_http_demo:  HTTP 服务启动中...

七、性能对比:Rust Server vs Node Server

在同样的机器上进行 10,000 并发请求测试(ab -n 10000 -c 200):

对比项 Node.js (Express) Rust (Tokio + Hyper)
吞吐量 ~9,000 req/s ~47,000 req/s
平均延迟 21.5ms 3.7ms
内存占用 ~140MB ~26MB
并发稳定性 高负载下易阻塞 稳定且线性扩展

Tokio 通过多线程事件循环 + 非阻塞 IO 实现近乎"原生性能",

这也是为什么越来越多后端项目(如 Discord、Fly.io、Amazon Lambda 内核)使用 Rust 的原因。


八、结语:Rust 异步的真正意义

Rust 的异步编程并不只是另一种"await语法糖",

而是一种系统级的高并发思维方式。

它让我们在不依赖 GC、不牺牲性能的前提下,

写出既优雅又安全的异步服务。

Rust 不是在追赶 Node.js,

而是在重新定义"高性能后端"的边界。

掌握 Tokio,就像给你的代码装上了"并发引擎"。

从此,Rust 不再只是写 CLI 的语言,

而是能承载真正业务流量的工业级后端框架。

相关推荐
小鹏linux2 小时前
Ubuntu 22.04 部署开源免费具有精美现代web页面的Casdoor账号管理系统
linux·前端·ubuntu·开源·堡垒机
starvapour7 小时前
Ubuntu切换到Fcitx5中文输入法
linux·运维·ubuntu
木欣欣粉皮8 小时前
解决Ubuntu 26.04的挂起状态唤醒问题
linux·运维·ubuntu
咸甜适中8 小时前
rust语言学习笔记Trait(八)Iterator(迭代器)
笔记·学习·rust
阿正的梦工坊8 小时前
【Typescript】08-keyof-typeof-索引访问类型
linux·ubuntu·typescript
xiaobobo33309 小时前
Ubuntu如何安装Vmware-tools和root用户
ubuntu·root用户·vmware-tools
轩Scott9 小时前
Ubuntu开机卡Logo?NVIDIA驱动修复全攻略
linux·ubuntu
yqcoder11 小时前
数据的“包装方式”:深入解析 HTTP Content-Type
网络·网络协议·http
caicai_xiaobai13 小时前
Ubuntu上Git安装步骤
linux·git·ubuntu