在 Ubuntu 上开始 Rust 网络编程,真正的门槛通常不是“会不会写代码”,而是环境、依赖和运行方式能不能一次打通。下面就用一个可直接运行的 TCP 服务示例,把安装 Rust、引入 Tokio、编写异步服务以及本地验证的过程串起来,帮助你判断自己已经完成了哪一步、下一步又该扩展到哪里。
先把 Ubuntu 上的 Rust 环境装完整
如果是从零开始,第一步先把 Rust 工具链装好。常见做法是直接使用 rustup 安装:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
安装结束后,还要刷新当前 shell 环境,否则新装的 cargo、rustc 可能暂时不可用:
source $HOME/.cargo/env
做完这一步,你就具备了创建项目、下载依赖和编译程序的基础条件。对后续网络编程来说,这一步虽然简单,但如果环境变量没生效,后面的项目初始化和运行都会卡住。
创建项目,并加入异步网络依赖
Rust 网络程序通常会先从一个独立项目开始。这里直接用 cargo 创建一个新工程:

cargo new rust_network_program
cd rust_network_program
项目创建完成后,需要为网络服务准备运行时依赖。原文选择的是 tokio,这也是 Rust 生态里最常见的异步运行时之一。把下面内容加入 Cargo.toml:
[dependencies]
tokio = { version = "1", features = ["full"] }
tokio 的作用不只是“让异步语法能跑起来”,它还提供了 TCP、定时器、任务调度等网络开发常用能力。这里启用 features = ["full"],相当于先把常用组件一次性配齐,便于快速搭出第一个服务。
用 Tokio 写一个最小可运行的 TCP 服务器
环境和依赖准备好之后,就可以在 src/main.rs 里写核心逻辑。这个示例程序做的事情很直接:监听 127.0.0.1:8080,接受客户端连接,读取请求后返回当前时间戳。

use tokio::net::{TcpListener, TcpStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use std::time::{SystemTime, UNIX_EPOCH};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
let mut buffer = [0; 1024];
loop {
let bytes_read = match socket.read(&mut buffer).await {
Ok(n) if n == 0 => return,
Ok(n) => n,
Err(e) => {
eprintln!("Failed to read from socket: {:?}", e);
return;
}
};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
let time_str = format!("Current time: {}\n", now);
if let Err(e) = socket.write_all(time_str.as_bytes()).await {
eprintln!("Failed to write to socket: {:?}", e);
return;
}
}
});
}
}
这段代码可以拆成几个关键点来理解:
1. 监听本地端口
TcpListener::bind("127.0.0.1:8080") 表示服务只监听本机的 8080 端口。也就是说,当前示例主要用于本地开发和验证,而不是直接对外提供访问。
2. 接收新连接
listener.accept().await? 会持续等待新的客户端连入。每接到一个连接,程序就拿到一个可读写的 socket。

3. 为每个连接启动异步任务
示例使用 tokio::spawn 为每个连接分配一个独立任务。这样多个客户端同时访问时,服务不会因为处理单个连接而阻塞整个监听循环,这是异步网络模型最直接的价值之一。
4. 读取数据并返回时间戳
服务端会先调用 socket.read(&mut buffer).await 读取客户端发送的数据。随后通过 SystemTime::now() 获取当前时间,并转换为基于 UNIX_EPOCH 的秒级时间戳,再用 socket.write_all(...).await 写回客户端。
这里的 bytes_read 虽然没有继续参与业务处理,但它仍然承担着判断连接状态的作用:如果读取结果为 0,就说明对端已经关闭连接,任务会直接退出。
5. 错误处理方式很适合入门验证
示例中对读写错误采用 eprintln! 输出,并在出错后结束当前连接任务。这种做法足够轻量,适合确认网络栈、异步运行时和基本收发逻辑是否正常工作。等后续进入正式项目,再考虑更细致的日志、超时和重试策略。
编译运行后,怎么确认服务真的可用
代码写完后,先直接编译并运行:
cargo run
如果依赖下载、编译和启动都正常,说明项目结构和 Tokio 运行时已经能够在 Ubuntu 上顺利工作。接下来要验证的是:服务是否真的监听成功,并且能正确回应客户端。
最简单的测试方式,是另开一个终端,用 telnet 或 nc 连接本地端口:
telnet 127.0.0.1 8080
或者:
nc 127.0.0.1 8080
连接建立后,服务端会返回当前时间戳。如果你已经看到响应,基本可以说明以下几件事都没问题:
- Rust 工具链已经正确安装;
cargo能正常解析并构建tokio依赖;- 本地 TCP 监听和连接建立是通的;
- 异步读取与写回逻辑已经跑通。
从入门角度看,这种验证方式非常有效,因为它不是停留在“代码编译过了”,而是进一步确认一个完整的网络请求和响应链路已经实际发生。
跑通最小示例后,可以往哪些方向扩展
这个 TCP 时间戳服务只是起点,但它已经覆盖了 Rust 网络编程最基础的几块内容:运行时、连接处理、异步读写和错误退出。接下来如果要做更贴近实际业务的服务,可以沿着生态继续扩展。
原文提到几个常见方向:
- 如果目标是 HTTP 服务器,可以考虑
hyper; - 如果要处理 WebSocket,可以使用
tokio-tungstenite; - 如果需要更高性能的协议解析,
nom和bytes是常见组合。
这几个库对应的是不同层次的问题:tokio 负责异步运行和底层 IO,hyper 更偏 HTTP 协议抽象,tokio-tungstenite 面向长连接实时通信,而 nom、bytes 更适合自定义协议和高效字节流处理。
换句话说,如果你已经能把本文这个例子跑通,就说明 Ubuntu 下的 Rust 网络开发基础环境已经具备。后续无论是做本地 TCP 服务、HTTP 接口,还是 WebSocket 应用,都可以在这个基础上继续往上搭,而不必再回头排查工具链和基础异步模型的问题。













