Rust 网络编程实战:用 Tokio 手写一个迷你 TCP 反向代理 (minginx)

Rust 网络编程实战:用 Tokio 手写一个迷你 TCP 反向代理 (minginx)

Nginx 作为强大的反向代理服务器,是我们日常开发中的得力助手。但你是否想过,它的核心原理------TCP流量转发,究竟是如何实现的?我们能否用 Rust 和 Tokio 亲手构建一个迷你版的 Nginx 呢?

本文旨在通过一个名为 minginx 的实战项目,带领读者从零开始,用不到100行代码实现一个功能完备的异步TCP反向代理。我们将深入探讨如何利用 tokio::spawn 处理并发连接,以及如何通过 tokio::io::copy 高效地在客户端和上游服务器之间建立双向数据流。为了验证我们的代理,我们还会搭建一个基于 axum 的后端Web服务。

读完本文,你不仅能理解TCP反向代理的本质,还能掌握一套使用Rust构建高性能网络服务的实用技能。

一、核心组件:TCP代理 (minginx.rs)

🗼 TCP代理 minginx.rs 文件

rust 复制代码
use std::sync::Arc;

use anyhow::Result;
use serde::{Deserialize, Serialize};
use tokio::{
    io,
    net::{TcpListener, TcpStream},
};
use tracing::{info, level_filters::LevelFilter, warn};
use tracing_subscriber::{Layer as _, fmt::Layer, layer::SubscriberExt, util::SubscriberInitExt};

#[derive(Debug, Deserialize, Serialize)]
struct Config {
    listen_addr: String,
    upstream_addr: String,
}

#[tokio::main]
async fn main() -> Result<()> {
    // proxy client traffic to upstream
    let layer = Layer::new().pretty().with_filter(LevelFilter::INFO);
    tracing_subscriber::registry().with(layer).init();

    let config = resolve_config();
    let config = Arc::new(config);
    info!("Upstream: {}", config.upstream_addr);
    info!("Listen: {}", config.listen_addr);

    let listener = TcpListener::bind(&config.listen_addr).await?;
    loop {
        let (client, addr) = listener.accept().await?;
        // let cloned_config = config.clone(); 阅读代码 方便使用 Arc::clone
        let cloned_config = Arc::clone(&config);
        info!("Accept connection from {}", addr);
        tokio::spawn(async move {
            let upstream = TcpStream::connect(&cloned_config.upstream_addr).await?;
            proxy(client, upstream).await?;
            Ok::<(), anyhow::Error>(())
        });
    }
}

async fn proxy(mut client: TcpStream, mut upstream: TcpStream) -> Result<()> {
    let (mut client_rd, mut client_wr) = client.split();
    let (mut upstream_rd, mut upstream_wr) = upstream.split();

    let client_to_upstream = io::copy(&mut client_rd, &mut upstream_wr);

    let upstream_to_client = io::copy(&mut upstream_rd, &mut client_wr);

    if let Err(e) = tokio::try_join!(client_to_upstream, upstream_to_client) {
        warn!("Error in proxy: {}", e)
    }
    Ok(())
}

fn resolve_config() -> Config {
    Config {
        listen_addr: "127.0.0.1:8081".to_string(),
        upstream_addr: "127.0.0.1:8080".to_string(),
    }
}

这段 Rust 代码实现了一个简单的 TCP 代理服务器 (TCP proxy)。

程序首先在本地 127.0.0.1:8081 地址上监听传入的 TCP 连接,当接收到一个新的客户端连接后,它会立即为该连接创建一个新的异步任务(tokio::spawn)。在这个新任务中,程序会连接到预设的上游服务器地址(127.0.0.1:8081),然后调用 proxy 函数。proxy 函数的核心功能是使用 io::copy 在客户端和上游服务器之间建立一个双向数据流 ,将从客户端收到的数据原封不动地转发给上游服务器,同时也将上游服务器返回的数据转发给客户端,从而完成代理的功能。

运行

bash 复制代码
rust-ecosystem-learning on  main [!?] is 📦 0.1.0 via 🦀 1.88.0 took 2m 4.9s 
➜ cargo run --example minginx
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/examples/minginx`
  2025-07-03T15:02:50.964166Z  INFO minginx: Upstream: 0.0.0.0:8080
    at examples/minginx.rs:26

  2025-07-03T15:02:50.964204Z  INFO minginx: Listen: 0.0.0.0:8081
    at examples/minginx.rs:27

二、后端验证:Web API 服务 (axum_serde.rs)

⚙️ 后端服务 axum_serde.rs 文件

rust 复制代码
use std::sync::{Arc, Mutex};

use anyhow::Result;
use axum::{
    Json, Router,
    extract::State,
    routing::{get, patch},
};
use serde::{Deserialize, Serialize};
use tokio::net::TcpListener;
use tracing::{info, instrument, level_filters::LevelFilter};
use tracing_subscriber::{
    Layer as _,
    fmt::{self, format::FmtSpan},
    layer::SubscriberExt,
    util::SubscriberInitExt,
};

#[derive(Debug, Clone, PartialEq, Serialize)]
struct User {
    name: String,
    age: u8,
    skills: Vec<String>,
}

#[derive(Debug, Clone, Deserialize)]
struct UserUpdate {
    age: Option<u8>,
    skills: Option<Vec<String>>,
}

#[tokio::main]
async fn main() -> Result<()> {
    let console = fmt::Layer::new()
        .with_span_events(FmtSpan::CLOSE)
        .pretty()
        .with_filter(LevelFilter::DEBUG);

    tracing_subscriber::registry().with(console).init();

    let user = User {
        name: "Alice".to_string(),
        age: 30,
        skills: vec!["Rust".to_string(), "Python".to_string()],
    };

    let user = Arc::new(Mutex::new(user));

    let addr = "0.0.0.0:8080";
    let listener = TcpListener::bind(addr).await?;
    info!("Listening on {}", addr);

    let app = Router::new()
        .route("/", get(user_handler))
        .route("/", patch(update_handler))
        .with_state(user);

    axum::serve(listener, app.into_make_service()).await?;

    Ok(())
}

#[instrument]
async fn user_handler(State(user): State<Arc<Mutex<User>>>) -> Json<User> {
    (*user.lock().unwrap()).clone().into()
}

#[instrument]
async fn update_handler(
    State(user): State<Arc<Mutex<User>>>,
    Json(user_update): Json<UserUpdate>,
) -> Json<User> {
    let mut user = user.lock().unwrap();
    if let Some(age) = user_update.age {
        user.age = age;
    }

    if let Some(skills) = user_update.skills {
        user.skills = skills;
    }
    (*user).clone().into()
}

这段 Rust 代码使用 axum 框架创建了一个简单的 Web API,用于查看和更新单个用户的个人资料。为了在多个并发请求之间安全地共享用户数据,它将用户状态包裹在 Arc<Mutex<User>> 中:Arc 允许多个任务共享数据所有权,而 Mutex 则确保同一时间只有一个任务能修改数据,从而防止数据竞争。该服务器运行在 tokio 异步运行时上,并开放了两个根路径 (/) 的 HTTP 接口:一个 GET 请求用于获取当前用户信息,另一个 PATCH 请求用于通过 JSON 数据更新用户的年龄或技能。此外,代码还配置了 tracing 库,以便为服务器事件和请求提供结构化的日志输出。

三、联调测试:验证代理功能

🚀 联调测试

Request:

http 复制代码
### update_handler
PATCH http://localhost:8081/ HTTP/1.1
Content-Type: application/json

{
    "skills": ["Go", "Python", "Java"]
}

Response:

bash 复制代码
HTTP/1.1 200 OK
content-type: application/json
content-length: 57
connection: close
date: Thu, 03 Jul 2025 15:04:15 GMT

{
  "name": "Alice",
  "age": 30,
  "skills": [
    "Go",
    "Python",
    "Java"
  ]
}

minginx 运行详情

bash 复制代码
rust-ecosystem-learning on  main [!?] is 📦 0.1.0 via 🦀 1.88.0 took 2m 4.9s 
➜ cargo run --example minginx
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/examples/minginx`
  2025-07-03T15:02:50.964166Z  INFO minginx: Upstream: 0.0.0.0:8080
    at examples/minginx.rs:26

  2025-07-03T15:02:50.964204Z  INFO minginx: Listen: 0.0.0.0:8081
    at examples/minginx.rs:27

  2025-07-03T15:03:25.513586Z  INFO minginx: Accept connection from 127.0.0.1:60605
    at examples/minginx.rs:34

  2025-07-03T15:04:15.831749Z  INFO minginx: Accept connection from 127.0.0.1:60821
    at examples/minginx.rs:34

axum_serde 运行详情

bash 复制代码
rust-ecosystem-learning on  main [!?] is 📦 0.1.0 via 🦀 1.88.0 took 3m 7.4s 
➜ cargo run --example axum_serde
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.26s
     Running `target/debug/examples/axum_serde`
  2025-07-03T15:02:43.690337Z  INFO axum_serde: Listening on 0.0.0.0:8080
    at examples/axum_serde.rs:51

  2025-07-03T15:03:25.516468Z  INFO axum_serde: close, time.busy: 31.6µs, time.idle: 24.0µs
    at examples/axum_serde.rs:68
    in axum_serde::update_handler with user: Mutex { data: User { name: "Alice", age: 30, skills: ["Rust", "Python"] }, poisoned: false, .. }, user_update: UserUpdate { age: None, skills: Some(["Go"]) }

  2025-07-03T15:04:15.836860Z  INFO axum_serde: close, time.busy: 8.62µs, time.idle: 12.9µs
    at examples/axum_serde.rs:68
    in axum_serde::update_handler with user: Mutex { data: User { name: "Alice", age: 30, skills: ["Go"] }, poisoned: false, .. }, user_update: UserUpdate { age: None, skills: Some(["Go", "Python", "Java"]) }

总结

通过 minginx 这个项目,我们从零开始,用短短几十行代码就实现了一个功能完备的 TCP 反向代理的核心。这不仅展示了 Rust 语言的强大表达力和安全性,更凸显了 Tokio 生态在构建高性能网络服务方面的巨大优势。我们看到,tokio::spawn 提供了简单的并发模型,而 io::copy 则优雅地处理了底层复杂的数据拷贝和流控制。

本文的 minginx 虽然简单,但它抓住了反向代理的精髓。在此基础上,可以进一步扩展,例如实现负载均衡、SSL/TLS卸载、健康检查等高级功能,最终构建出企业级的网络中间件。

希望这个小项目能为你打开一扇通往 Rust 网络编程世界的大门,激发你探索更多可能性的热情。

参考

相关推荐
轻语呢喃3 分钟前
JavaScript :字符串模板——优雅编程的基石
前端·javascript·后端
MikeWe8 分钟前
Paddle张量操作全解析:从基础创建到高级应用
后端
岫珩17 分钟前
Ubuntu系统关闭防火墙的正确方式
后端
心之语歌21 分钟前
Java高效压缩技巧:ZipOutputStream详解
java·后端
不死的精灵1 小时前
【Java21】在spring boot中使用ScopedValue
java·spring boot·后端
M1A12 小时前
TCP/IP协议精解:IP协议——互联网世界的邮政编码系统
后端·网络协议·tcp/ip
逸风尊者2 小时前
开发易掌握的知识:GeoHash查找附近空闲车辆
java·后端
程序猿阿越3 小时前
Kafka源码(一)Controller选举与创建Topic
java·后端·源码
程序员爱钓鱼3 小时前
Go语言项目工程化 — 常见开发工具与 CI/CD 支持
开发语言·后端·golang·gin
Jiude3 小时前
MinIO 社区版被故意阉割,Web管理功能全面移除。我来试试国产RustFS
后端·docker·架构