WebSocket in Rust: Building 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 WebSocket on Rust is a Powerful Tandem



Modern web applications increasingly require instant data transfer: chats, online games, stock quotes, notifications. Traditional HTTP with its request-response model is inefficient here — it creates extra load and delays. The WebSocket protocol comes to the rescue, establishing a persistent bidirectional connection between client and server.



The Rust language, with its emphasis on performance, memory safety, and zero-cost abstractions, is ideal for building high-load real-time systems. In this article, we will explore how to build a WebSocket server in Rust using the tokio-tungstenite library and examine key patterns for real-time applications.



WebSocket Basics and Environment Setup



What is WebSocket?

WebSocket is a protocol that works over TCP. It allows the server and client to exchange messages in real time without repeated HTTP handshakes. The connection starts as an HTTP request with an upgrade to WebSocket, after which data is transmitted in frames.



Installing Dependencies

For our work, we will need tokio (async runtime) and tokio-tungstenite (WebSocket implementation). Create a new project:



cargo new websocket_democd websocket_demo


Add dependencies to Cargo.toml:



[dependencies]tokio = { version = "1", features = ["full"] }tokio-tungstenite = "0.21"futures-util = "0.3"


Creating a Simple WebSocket Server



Basic Echo Server

Let's start with a simple server that accepts connections and sends back received messages (echo mode). This is the classic "Hello, World" for WebSocket.



use tokio::net::TcpListener;use tokio_tungstenite::accept_async;use tokio_tungstenite::tungstenite::Message;use futures_util::StreamExt;

#[tokio::main]async fn main() { let addr = "127.0.0.1:8080"; let listener = TcpListener::bind(addr).await.expect("Failed to bind address"); println!("Server started on {}", addr);

while let Ok((stream, peer)) = listener.accept().await { println!("New connection: {}", peer); tokio::spawn(handle_connection(stream)); }}

async fn handle_connection(stream: tokio::net::TcpStream) { let ws_stream = accept_async(stream) .await .expect("Error during WebSocket handshake");

let (_, mut read) = ws_stream.split();

while let Some(msg) = read.next().await { match msg { Ok(Message::Text(text)) => { println!("Received: {}", text); // Here you could send a response, but for simplicity we skip it } Ok(Message::Close(_)) => break, _ => {} } }}


This code starts a TCP server on port 8080. For each new connection, an async task handle_connection is created, which performs the WebSocket handshake and reads incoming messages.



Adding Message Sending

To make a full-fledged echo server, we need to split the stream into read and write parts. We use futures_util::StreamExt and SinkExt:



use futures_util::SinkExt;

async fn handle_connection(stream: tokio::net::TcpStream) { let ws_stream = accept_async(stream) .await .expect("Error during WebSocket handshake");

let (mut write, mut read) = ws_stream.split();

while let Some(msg) = read.next().await { match msg { Ok(Message::Text(text)) => { let echo = format!("Echo: {}", text); write.send(Message::Text(echo.into())).await.unwrap(); } Ok(Message::Close(_)) => break, _

Blogs

Book Recommendations