Rust网络编程:编写快速且安全的TCP服务器

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么Rust是网络编程的新王者?



你是否曾经用C或C++编写过网络服务器,却因为忘记互斥锁而遇到段错误?或者在与Go中的数据竞争作斗争,试图弄清楚为什么goroutine“挂起”了?Rust一劳永逸地解决了这些问题。它的所有权系统和“无畏并发”模型使Rust网络编程不仅安全,而且性能惊人。



在本文中,我们将探讨如何使用标准库(std::net)以及基于Tokio的现代异步栈来创建TCP和UDP服务器。你将看到,Rust不仅是系统编程语言,也是编写高负载网络服务的绝佳工具。



基础:std::net——5分钟搭建TCP回显服务器



我们从经典的回显服务器开始。它接受TCP连接,读取数据并将其发送回客户端。在Rust中,这出奇地简洁。



use std::io::{Read, Write};use std::net::{TcpListener, TcpStream};use std::thread;

fn handle_client(mut stream: TcpStream) { let mut buffer = [0; 1024]; loop { match stream.read(&mut buffer) { Ok(0) => { println!("客户端已断开"); break; } Ok(n) => { if let Err(e) = stream.write_all(&buffer[..n]) { eprintln!("写入错误: {}", e); break; } } Err(e) => { eprintln!("读取错误: {}", e); break; } } }}

fn main() -> std::io::Result<()> { let listener = TcpListener::bind("127.0.0.1:7878")?; println!("服务器已启动,监听端口 7878");

for stream in listener.incoming() { match stream { Ok(stream) => { thread::spawn(|| handle_client(stream)); } Err(e) => { eprintln!("连接错误: {}", e); } } } Ok(())}


这里发生了什么?TcpListener::bind创建了一个监听套接字。incoming()方法返回一个传入连接的迭代器。对于每个新客户端,我们启动一个单独的线程——thread::spawn。在handle_client内部,我们将数据读入缓冲区,并通过write_all将其发送回去。



重要提示:Rust保证TcpStream不会同时从两个线程中被意外使用——编译器根本不允许这样做。这就是无畏并发。



这种方法的缺点

  • 扩展性:每个客户端一个单独的线程。当有10,000个客户端时,操作系统会不堪重负。
  • 资源:默认线程栈大小为2MB。10,000个客户端 = 20GB内存。


异步编程可以解决这些问题。



异步革命:Tokio和async/await



Rust没有内置的异步运行时(如Go或Node.js)。相反,它提供了Future trait和async/await关键字,运行时由你选择。最流行的是Tokio。它不仅是一个库,更是一个完整的生态系统:TCP/UDP、文件I/O、定时器、通道,甚至还有自己的调度器。



让我们用Tokio重写我们的回显服务器。



Tokio回显服务器

在Cargo.toml中添加:

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


现在来看代码:



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

#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> {

博客

书籍推荐