Network Programming in Rust: Writing a Fast and Secure TCP Server

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why Rust is the New King of Network Programming?

\n\n

Have you ever written a network server in C or C++ and caught a segmentation fault due to a forgotten mutex? Or struggled with data races in Go, trying to understand why a goroutine "hung"? Rust solves these problems once and for all. Its ownership system and fearless concurrency model make network programming in Rust not only safe but also incredibly performant.

\n\n

In this article, we'll break down how to create TCP and UDP servers in Rust, using both the standard library (std::net) and the modern async stack based on Tokio. You'll see that Rust is not just a language for systems programming, but also an excellent tool for writing high-load network services.

\n\n

Basics: std::net — TCP Echo Server in 5 Minutes

\n\n

Let's start with a classic — an echo server. It accepts a TCP connection, reads data, and sends it back to the client. In Rust, this is surprisingly concise.

\n\n
use std::io::{Read, Write};\nuse std::net::{TcpListener, TcpStream};\nuse std::thread;\n\nfn handle_client(mut stream: TcpStream) {\n    let mut buffer = [0; 1024];\n    loop {\n        match stream.read(&mut buffer) {\n            Ok(0) => {\n                println!("Client disconnected");\n                break;\n            }\n            Ok(n) => {\n                if let Err(e) = stream.write_all(&buffer[..n]) {\n                    eprintln!("Write error: {}", e);\n                    break;\n                }\n            }\n            Err(e) => {\n                eprintln!("Read error: {}", e);\n                break;\n            }\n        }\n    }\n}\n\nfn main() -> std::io::Result<()> {\n    let listener = TcpListener::bind("127.0.0.1:7878")?;\n    println!("Server started on port 7878");\n\n    for stream in listener.incoming() {\n        match stream {\n            Ok(stream) => {\n                thread::spawn(|| handle_client(stream));\n            }\n            Err(e) => {\n                eprintln!("Connection error: {}", e);\n            }\n        }\n    }\n    Ok(())\n}\n
\n\n

What's happening here? TcpListener::bind creates a listening socket. The incoming() method returns an iterator over incoming connections. For each new client, we spawn a separate thread — thread::spawn. Inside handle_client, we read data into a buffer and send it back via write_all.

\n\n

Important: Rust guarantees that TcpStream won't accidentally be used from two threads simultaneously — the compiler simply won't allow it. This is fearless concurrency.

\n\n

Problems with This Approach

\n
    \n
  • Scaling: Each client is a separate thread. With 10,000 clients, the OS will simply choke.
  • \n
  • Resources: The default thread stack is 2 MB. 10,000 clients = 20 GB of memory.
  • \n
\n\n

Asynchronous programming comes to the rescue.

\n\n

Async Revolution: Tokio and async/await

\n\n

Rust does not have a built-in async runtime (like Go or Node.js). Instead, it provides the Future trait and the async/await keywords, and the choice of runtime is up to you. The most popular is Tokio. It's not just a library, it's a whole ecosystem: TCP/UDP, file I/O, timers, channels, and even its own scheduler.

\n\n

Let's rewrite our echo server with Tokio.

\n\n

Tokio Echo Server

\n

Add to Cargo.toml:

\n
[dependencies]\ntokio = { version = "1", features = ["full"] }
\n\n

Now the code:

\n\n
use tokio::io::{AsyncReadExt, AsyncWriteExt};\nuse tokio::net::TcpListener;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n                

Blogs

Book Recommendations