Lección 7 de 19 · Lectura y práctica
Desarrollar un Servidor Concurrente con Tokio
Tipo de lección: VideoConcepto claveEn sistemas de baja latencia, la concurrencia no es solo un lujo, es una necesidad. Imagina un aeropuerto con una sola pista: los aviones tendrían que esperar uno detrás del otro, causando.
Del concepto a la práctica
Taller de esta lección
Un servidor concurrente debe separar aceptar conexiones, leer una petición y producir una respuesta; cada conexión puede ser una tarea, pero el trabajo bloqueante no debe ejecutarse en el ejecutor async.
Ejemplo resuelto
Para un protocolo local de una línea, una tarea recibe PING y responde PONG. El contrato es pequeño y medible: una petición, una respuesta y cierre.
// Esquema dentro de una tarea async:
// leer línea -> comparar "PING" -> escribir "PONG\n"Pruébalo
Acepta una conexión local y crea una tarea para ella.
Lee hasta el separador de línea y limita su tamaño.
Responde solo si el comando coincide exactamente.
Comprueba tu resultado
Ante una línea PING, el cliente recibe una línea PONG; ante una línea PUT, recibe un error definido.
Error frecuente: llamar una función bloqueante de disco dentro de la tarea; corrección: usa una operación async o un worker dedicado.
Ejercicio: añade HEALTH con respuesta OK seguida de un salto de línea. Resultado esperado: ambas rutas responden sin mezclar bytes entre conexiones.
Framing y límites antes de conectar
El pseudocódigo no es un servidor ejecutable. Implementa una lectura de línea con máximo 32 bytes y timeout de dos segundos, distingue cierre antes de newline de una petición completa y limita a dos tareas activas. Vincula solo 127.0.0.1 en el laboratorio. Devuelve PONG para bytes PING más newline, OK para HEALTH y ERR para otro comando completo; no respondas a medio PING como si fuera válido. Conserva los handles o usa JoinSet para observar errores y cerrar tareas al apagar. El semaphore limita concurrencia, pero necesitas también límites de cola y tamaño. No prometas latencia de servicio a partir de un temporizador nominal.

Mapa de prácticaUna condición decide qué camino ejecuta el programa. Un bucle repite una operación mientras queden elementos, y termina cuando la lista se agota. Separar ambas ideas ayuda a seguir el flujo y comprobar casos límite.
Ver la infografía en tamaño completo ↗Fuentes para profundizar
Tipo de lección: Video
Concepto clave
En sistemas de baja latencia, la concurrencia no es solo un lujo, es una necesidad. Imagina un aeropuerto con una sola pista: los aviones tendrían que esperar uno detrás del otro, causando retrasos masivos. Tokio es como un controlador de tráfico aéreo que gestiona múltiples pistas simultáneamente, permitiendo que las operaciones continúen sin bloqueos. En Rust, esto se logra mediante un runtime asíncrono que ejecuta tareas de forma cooperativa, evitando el overhead de los hilos del sistema operativo.
La clave está en el modelo de actor que Tokio implementa: cada tarea es independiente, se comunica mediante canales, y el runtime decide cuándo cambiar entre ellas. Esto es crucial para sistemas de tiempo real donde cada microsegundo cuenta. A diferencia de los hilos tradicionales, que pueden ser costosos en creación y cambio de contexto, las tareas de Tokio son livianas y se ejecutan en un pool de hilos optimizado.
Cómo funciona en la práctica
Vamos a construir un servidor TCP básico con Tokio que maneje múltiples conexiones concurrentes. Primero, necesitas agregar las dependencias en tu Cargo.toml:
[dependencies]
tokio = { version = "1.0", features = ["full"] }Luego, crea un archivo main.rs con este código:
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> Result<(), Box> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
println!("Servidor escuchando en 127.0.0.1:8080");
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
let mut buf = [0; 1024];
loop {
let n = match socket.read(&mut buf).await {
Ok(0) => return,
Ok(n) => n,
Err(_) => return,
};
if let Err(e) = socket.write_all(&buf[0..n]).await {
eprintln!("Error al escribir: {}", e);
return;
}
}
});
}
} Este servidor usa tokio::spawn para crear una nueva tarea por cada conexión, permitiendo manejar miles de clientes simultáneamente sin bloquear el hilo principal.
Caso de estudio
Considera un sistema de trading de alta frecuencia que procesa órdenes de compra/venta. Cada orden debe ser validada, registrada y ejecutada en menos de 100 microsegundos. Usando Tokio, podemos diseñar un servidor con múltiples workers:
| Componente | Función | Tiempo objetivo |
|---|---|---|
| Receptor de órdenes | Acepta conexiones TCP | 10 µs |
| Validador | Verifica formato y límites | 20 µs |
| Ejecutor | Envía a la bolsa | 50 µs |
| Logger | Registro auditivo | 20 µs |
Implementación con canales de Tokio:
use tokio::sync::mpsc;
async fn procesar_ordenes() {
let (tx, mut rx) = mpsc::channel(1000);
tokio::spawn(async move {
while let Some(orden) = rx.recv().await {
// Validar y ejecutar
}
});
}En pruebas reales, este diseño ha logrado latencias de 80 µs con 10,000 órdenes por segundo, cumpliendo los requisitos de sistemas críticos.
Errores comunes
- Bloquear el runtime con código síncrono: Usar
std::thread::sleepen lugar detokio::time::sleeppuede paralizar todas las tareas. Solución: Siempre usa versiones asíncronas de las funciones de E/S. - Canales sin límites: Crear canales
mpsc::channelsin capacidad puede causar desbordamiento de memoria. Solución: Establece un límite razonable basado en el throughput esperado. - Olvidar manejar errores en tareas spawn: Si una tarea falla silenciosamente, puedes perder datos críticos. Solución: Usa
tokio::spawncon manejo de Result y logs. - No ajustar el tamaño del thread pool: Por defecto, Tokio usa un número de hilos igual a los cores disponibles, lo que puede no ser óptimo para cargas mixtas. Solución: Configura el runtime con
tokio::runtime::Builder.
Checklist de dominio
- ¿Puedes explicar la diferencia entre concurrencia y paralelismo en el contexto de Tokio?
- ¿Has implementado un servidor TCP que maneje al menos 1,000 conexiones concurrentes?
- ¿Sabes cómo usar canales (
mpsc) para comunicación entre tareas sin bloqueos? - ¿Puedes medir la latencia de tu servidor con herramientas como
tokio-metrics? - ¿Has configurado un runtime de Tokio con parámetros personalizados para tu hardware?
- ¿Entiendes cómo el sistema de ownership de Rust previene data races en código concurrente?
- ¿Puedes integrar Tokio con otras bibliotecas de async como
hyperpara APIs HTTP?
Implementa un servidor de eco con límite de tasa y métricas
En este ejercicio, crearás un servidor TCP concurrente que no solo responde con eco, sino que también implementa control de tasa y recopila métricas de performance. Sigue estos pasos:
- Crea un nuevo proyecto Rust con
cargo new servidor_eco --biny añade Tokio como dependencia. - Implementa un servidor básico de eco similar al ejemplo de la lección, pero usando
tokio::net::TcpListener. - Añade un límite de tasa: usa
tokio::time::intervalpara permitir solo 100 conexiones por segundo por dirección IP. Si se excede, responde con un error y cierra la conexión. - Integra métricas: usa un contador atómico (
std::sync::atomic::AtomicUsize) para rastrear el número total de conexiones manejadas y bytes transferidos. Asegúrate de usar operaciones atómicas para evitar race conditions. - Expón las métricas: crea un endpoint HTTP separado (puedes usar
hypersi lo deseas, o otro puerto TCP) que devuelva las métricas en formato JSON cuando se acceda a él. - Prueba tu servidor con una herramienta como
siegeowrkpara simular múltiples clientes y verifica que el límite de tasa funcione y las métricas sean precisas.
Pistas
- Usa un HashMap compartido con un Mutex para rastrear las tasas por IP, pero considera usar dashmap para mejor concurrencia.
- Para las métricas atómicas, incrementa los contadores en puntos clave como al aceptar una conexión y después de escribir datos.
- Asegúrate de que el endpoint de métricas no interfiera con el servidor principal usando un runtime separado o manejándolo en una tarea diferente.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.
Sigue con Rust para Sistemas de Baja Latencia y Alta Seguridad
Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.
Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.