Introdução: por que WebSocket em Rust é uma combinação poderosa
Aplicações web modernas cada vez mais exigem transmissão instantânea de dados: chats, jogos online, cotações de bolsa, notificações. O HTTP tradicional com seu modelo de requisição-resposta é ineficiente aqui — ele cria carga e latência desnecessárias. O protocolo WebSocket vem em socorro, estabelecendo uma conexão bidirecional permanente entre cliente e servidor.
A linguagem Rust com seu foco em desempenho, segurança de memória e abstrações de custo zero é ideal para criar sistemas de alta carga em tempo real. Neste artigo, vamos analisar como construir um servidor WebSocket em Rust usando a biblioteca tokio-tungstenite e examinar os principais padrões para aplicações em tempo real.
Fundamentos do WebSocket e preparação do ambiente
O que é WebSocket?
WebSocket é um protocolo que opera sobre TCP. Ele permite que servidor e cliente troquem mensagens em tempo real sem a necessidade de handshakes HTTP repetidos. A conexão começa como uma requisição HTTP com upgrade para WebSocket, após o que os dados são transmitidos em frames.
Instalação de dependências
Para funcionar, precisaremos de tokio (runtime assíncrono) e tokio-tungstenite (implementação WebSocket). Crie um novo projeto:
cargo new websocket_democd websocket_demoAdicione as dependências no Cargo.toml:
[dependencies]tokio = { version = "1", features = ["full"] }tokio-tungstenite = "0.21"futures-util = "0.3"Criando um servidor WebSocket simples
Servidor echo básico
Vamos começar com um servidor simples que aceita conexões e envia de volta as mensagens recebidas (modo echo). Este é o clássico "Hello, World" para 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("Não foi possível vincular o endereço"); println!("Servidor iniciado em {}", addr);
while let Ok((stream, peer)) = listener.accept().await { println!("Nova conexão: {}", peer); tokio::spawn(handle_connection(stream)); }}
async fn handle_connection(stream: tokio::net::TcpStream) { let ws_stream = accept_async(stream) .await .expect("Erro no handshake WebSocket");
let (_, mut read) = ws_stream.split();
while let Some(msg) = read.next().await { match msg { Ok(Message::Text(text)) => { println!("Recebido: {}", text); // Aqui poderia enviar uma resposta, mas por simplicidade vamos pular } Ok(Message::Close(_)) => break, _ => {} } }}Este código inicia um servidor TCP na porta 8080. Para cada nova conexão, é criada uma tarefa assíncrona handle_connection que realiza o handshake WebSocket e lê as mensagens recebidas.
Adicionando envio de mensagens
Para fazer um servidor echo completo, precisamos dividir o stream em leitura e escrita. Usamos futures_util::StreamExt e SinkExt:
use futures_util::SinkExt;
async fn handle_connection(stream: tokio::net::TcpStream) { let ws_stream = accept_async(stream) .await .expect("Erro no handshake WebSocket");
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, _