Introducción: ¿Por qué Rust es el candidato ideal para tiempo real?
\n\nImagina esta situación: estás escribiendo un servidor de chat, un terminal bursátil o un sistema de monitoreo en Rust. Los usuarios quieren recibir actualizaciones al instante. A nadie le gusta presionar el botón "Actualizar" manualmente — eso es cosa del pasado. Pero, ¿cómo organizar la entrega de datos del servidor al cliente?
\n\nDurante mucho tiempo, la única forma fue el long polling — un truco elegante pero un poco "chapucero". Luego llegaron los WebSocket y revolucionaron el mundo del desarrollo web. Y ahora tenemos Rust — un lenguaje que permite exprimir al máximo ambos enfoques sin sacrificar la seguridad de la memoria.
\n\nEn este artículo no solo compararemos long polling y WebSocket en Rust — escribiremos ejemplos funcionales, los ejecutaremos y veremos qué sucede bajo el capó. Si estás desarrollando una aplicación en tiempo real en Rust y dudas sobre qué tecnología elegir — estás en el lugar correcto.
\n\nQué es Long Polling explicado de forma sencilla
\n\nLong polling es una técnica en la que el cliente abre una solicitud HTTP al servidor y... espera. El servidor no responde de inmediato, sino que mantiene la conexión abierta hasta que ocurra un nuevo evento o se agote el tiempo de espera.
\n\nImagina que llamas a un amigo y le dices: "Llámame cuando llegue el paquete" — y te quedas al teléfono una hora. Eso es long polling. El polling normal es cuando vuelves a llamar cada 5 minutos: "¿Ya llegó?"
\n\nImplementación de Long Polling en Rust con Actix-web
\n\nEscribamos un servidor muy simple en actix-web que use long polling. Tendremos una cola de mensajes — cuando aparece un nuevo mensaje, todos los "esperadores" lo reciben.
\n\nuse actix_web::{web, App, HttpServer, HttpRequest, HttpResponse};\nuse std::sync::Mutex;\nuse tokio::sync::broadcast;\n\nstruct AppState {\n tx: broadcast::Sender<String>,\n}\n\nasync fn long_poll_handler(data: web::Data<Mutex<AppState>>) -> HttpResponse {\n let mut rx = {\n let state = data.lock().unwrap();\n state.tx.subscribe()\n };\n\n // Esperamos un nuevo mensaje durante 30 segundos\n match tokio::time::timeout(\n std::time::Duration::from_secs(30),\n rx.recv()\n ).await {\n Ok(Ok(msg)) => HttpResponse::Ok().body(msg),\n Ok(Err(_)) => HttpResponse::InternalServerError().finish(),\n Err(_) => HttpResponse::NoContent().finish(), // tiempo de espera agotado\n }\n}\n\n#[actix_web::main]\nasync fn main() -> std::io::Result<()> {\n let (tx, _) = broadcast::channel::<String>(100);\n\n HttpServer::new(move || {\n App::new()\n .app_data(web::Data::new(Mutex::new(AppState { tx: tx.clone() })))\n .route("/poll", web::get().to(long_poll_handler))\n })\n .bind("127.0.0.1:8080")?\n .run()\n .await\n}\n\nEl cliente simplemente se quedará colgado en GET /poll. Cuando llegue un mensaje — el servidor responderá. Si no hay nada en 30 segundos — devolverá 204 No Content, y el cliente volverá a solicitar.
Ventajas:
\n- \n
- Funciona a través de HTTP normal — no se necesitan servidores proxy con soporte WebSocket \n
- Facilidad de integración con APIs REST existentes \n
- Ideal para redes corporativas donde los proxies bloquean WebSocket \n
Desventajas:
\n- \n
- Cada "polling" mantiene una conexión — con 10,000 clientes son 10,000 conexiones TCP abiertas \n
- Latencia: el cliente igualmente se reconecta después del tiempo de espera \n
- El servidor gasta recursos manteniendo solicitudes "colgadas" \n
WebSocket en Rust: cuando quieres velocidad
\n\nWebSocket ya no es una "llamada y espera", sino un canal bidireccional completo. El servidor y el cliente pueden intercambiar mensajes en cualquier momento, sin la sobrecarga de los encabezados HTTP.
\n\nEn Rust, el ecosistema WebSocket es rico: tokio-tungstenite para trabajo de bajo nivel, actix-web con soporte integrado