WebSocket in Rust: Building Advanced-Level Real-Time Applications

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

Introduction: Why Rust for WebSocket?



Modern real-time applications — chats, trading terminals, online games, monitoring systems — require high performance, minimal latency, and reliability. Rust, with its ownership system, zero-cost abstractions, and the Tokio asynchronous runtime, becomes an ideal choice for building scalable WebSocket servers.



In this article, we will go beyond a simple echo server and explore advanced techniques: managing thousands of concurrent connections, thread pools, frameworks for building complex logic, as well as patterns for horizontal scaling. The material is intended for developers already familiar with the basics of Rust and asynchronous programming.



Architecture of a High-Load WebSocket Server



1. Asynchronous Foundation: Tokio and warp



For production-level applications, we use the combination of Tokio (asynchronous runtime) and warp (a filter-based web framework). Unlike simple solutions, warp provides flexible route composition and built-in WebSocket support.



use warp::Filter;use futures_util::{SinkExt, StreamExt};use tokio::sync::broadcast;

#[tokio::main]async fn main() { let (tx, _rx) = broadcast::channel::<String>(100); let ws_route = warp::path("ws") .and(warp::ws()) .and(with_sender(tx.clone())) .map(|ws: warp::ws::Ws, tx| { ws.on_upgrade(move |socket| handle_connection(socket, tx)) });

let health_route = warp::path("health") .map(|| warp::reply::json(&serde_json::json!({"status": "ok"})));

let routes = ws_route.or(health_route); warp::serve(routes).run(([0, 0, 0, 0], 8080)).await;}

fn with_sender(tx: broadcast::Sender<String>) -> impl Filter<Extract = (broadcast::Sender<String>,), Error = std::convert::Infallible> + Clone { warp::any().map(move || tx.clone())}


Key points:

  • We use broadcast::channel to broadcast messages to all connected clients (pub/sub pattern).
  • A channel with a buffer of 100 messages prevents blocking during sudden traffic spikes.
  • The with_sender filter injects the sender into the handler via a closure.


2. Connection State Management



At an advanced level, it is not enough to simply send messages — you need to manage the lifecycle of each connection, track rooms, authentication, and timeouts. We use a HashMap with Arc<Mutex<>> for safe access from multiple tasks.



use std::collections::HashMap;use std::sync::Arc;use tokio::sync::Mutex;use warp::ws::Message;

type Clients = Arc<Mutex<HashMap<String, Vec<tokio::sync::mpsc::UnboundedSender<Message>>>>>;

async fn handle_connection(ws: warp::ws::WebSocket, clients: Clients, room_id: String) { let (mut ws_tx, mut ws_rx) = ws.split(); let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<Message>(); // Register the client in the room { let mut map = clients.lock().await; map.entry(room_id.clone()).or_insert_with(Vec::new).push(tx); }

// Task for sending messages to the client let send_task = tokio::spawn(async move { while let Some(msg) = rx.recv().await { if ws_tx.send(msg).await.is_err() { break; } } });

// Task for receiving messages from the client let receive_task = tokio::spawn(async move { while let Some(Ok(msg)) = ws_rx.next().await { // Here you can process the incoming message // For example, parse JSON, check permissions println!("Received: {:?}", msg); } });

// Wait for either task to complete

Blogs

Book Recommendations