Programmation réseau en Rust : écrire un serveur TCP rapide et sécurisé

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Pourquoi Rust est-il le nouveau roi de la programmation réseau ?

\n\n

Avez-vous déjà écrit un serveur réseau en C ou C++ et attrapé une erreur de segmentation à cause d'un mutex oublié ? Ou lutté contre des conditions de course en Go, en essayant de comprendre pourquoi une goroutine s'est « bloquée » ? Rust résout ces problèmes une fois pour toutes. Son système de possession (ownership) et son modèle de concurrence « sans peur » (fearless concurrency) rendent la programmation réseau en Rust non seulement sûre, mais aussi incroyablement performante.

\n\n

Dans cet article, nous allons voir comment créer des serveurs TCP et UDP en Rust, en utilisant à la fois la bibliothèque standard (std::net) et la pile asynchrone moderne basée sur Tokio. Vous verrez que Rust n'est pas seulement un langage pour la programmation système, mais aussi un excellent outil pour écrire des services réseau à forte charge.

\n\n

Les bases : std::net — Serveur écho TCP en 5 minutes

\n\n

Commençons par un classique — le serveur écho. Il accepte une connexion TCP, lit les données et les renvoie au client. En Rust, cela se fait de manière étonnamment 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 déconnecté");\n                break;\n            }\n            Ok(n) => {\n                if let Err(e) = stream.write_all(&buffer[..n]) {\n                    eprintln!("Erreur d'écriture : {}", e);\n                    break;\n                }\n            }\n            Err(e) => {\n                eprintln!("Erreur de lecture : {}", 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!("Serveur démarré sur le 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!("Erreur de connexion : {}", e);\n            }\n        }\n    }\n    Ok(())\n}\n
\n\n

Que se passe-t-il ici ? TcpListener::bind crée un socket d'écoute. La méthode incoming() retourne un itérateur sur les connexions entrantes. Pour chaque nouveau client, nous lançons un thread séparé — thread::spawn. Dans handle_client, nous lisons les données dans un tampon et les renvoyons via write_all.

\n\n

Important : Rust garantit que TcpStream ne sera pas accidentellement utilisé depuis deux threads simultanément — le compilateur ne le permettra tout simplement pas. C'est ce qu'on appelle la concurrence sans peur.

\n\n

Problèmes de cette approche

\n
    \n
  • Passage à l'échelle : chaque client est un thread séparé. Avec 10 000 clients, le système d'exploitation sature.
  • \n
  • Ressources : la pile de thread par défaut est de 2 Mo. 10 000 clients = 20 Go de mémoire.
  • \n
\n\n

La programmation asynchrone vient à la rescousse.

\n\n

Révolution asynchrone : Tokio et async/await

\n\n

Rust n'a pas d'environnement d'exécution asynchrone intégré (comme Go ou Node.js). Au lieu de cela, il fournit les traits Future et les mots-clés async/await, et le choix du runtime vous appartient. Le plus populaire est Tokio. Ce n'est pas seulement une bibliothèque, c'est tout un écosystème : TCP/UDP, entrées-sorties fichiers, minuteurs, canaux et même son propre planificateur.

\n\n

Réécrivons notre serveur écho avec Tokio.

\n\n

Serveur écho Tokio

\n

Ajoutez dans Cargo.toml :

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

Maintenant le 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