En el ecosistema de Rust, la comunicación a través de la red se gestiona mediante el módulo std::net, el cual proporciona abstracciones sobre los sockets TCP. Para implementar un servidor, se utiliza TcpListener, cuya función es “escuchar” una dirección IP y un puerto específicos mediante el método bind. Si el puerto ya está ocupado o no hay permisos suficientes, bind devolverá un error que debe ser manejado para evitar el colapso de la aplicación. Una vez vinculado, el método incoming() proporciona un iterador sobre las conexiones entrantes; cada vez que un cliente se conecta, este iterador entrega un TcpStream.
TcpStream es la representación de la conexión bidireccional establecida. Es una estructura fundamental porque implementa los rasgos Read y Write, lo que permite enviar y recibir bytes de forma fluida. Sin embargo, un error común en implementaciones básicas es procesar cada conexión de forma secuencial en el hilo principal. Dado que métodos como accept() o las operaciones de lectura son bloqueantes, un cliente lento o malintencionado podría impedir que el servidor acepte nuevas conexiones. Para solucionar esto, es necesario despachar cada TcpStream a un hilo independiente mediante std::thread::spawn, permitiendo que el bucle principal de listener.incoming() siga disponible para nuevos clientes.
Para optimizar la comunicación, es vital configurar parámetros del socket. El método set_nodelay(true) desactiva el algoritmo de Nagle, lo cual es crítico en aplicaciones donde la latencia es más importante que el aprovechamiento del ancho de banda (como en protocolos de chat o juegos). Asimismo, el uso de set_read_timeout previene que un hilo se quede esperando eternamente por datos que nunca llegarán. Para una lectura eficiente de texto, se recomienda envolver el TcpStream en un BufReader, permitiendo el uso de métodos convenientes como read_line que busca el carácter de nueva línea (\n).
use std::io::{BufRead, BufReader, Write};
use std::net::{TcpListener, TcpStream, Shutdown};
use std::thread;
use std::time::Duration;
fn main() -> std::io::Result<()> {
let address = "127.0.0.1:8080";
// TcpListener::bind intenta vincular el socket a la dirección especificada
let listener = TcpListener::bind(address)?;
println!("Servidor escuchando en {}", address);
// incoming() es un iterador que bloquea hasta que llega una nueva conexión
for stream_result in listener.incoming() {
match stream_result {
Ok(client_stream) => {
// Despachamos el cliente a un hilo para no bloquear el loop principal
// Usamos 'move' para transferir la propiedad de client_stream al hilo
thread::spawn(move || {
if let Err(e) = handle_client(client_stream) {
eprintln!("Error manejando cliente: {}", e);
}
});
}
Err(e) => eprintln!("Error en la conexión entrante: {}", e),
}
}
Ok(())
}
fn handle_client(mut stream: TcpStream) -> std::io::Result<()> {
// Deshabilitar algoritmo de Nagle para reducir latencia
stream.set_nodelay(true)?;
// Configurar un timeout para evitar que hilos queden colgados
stream.set_read_timeout(Some(Duration::from_secs(5)))?;
let peer_addr = stream.peer_addr()?;
println!("Conexión establecida con: {}", peer_addr);
// BufReader permite leer de forma eficiente por líneas
let mut reader = BufReader::new(&stream);
let mut line = String::new();
// Leer hasta encontrar un '\n'
let bytes_read = reader.read_line(&mut line)?;
if bytes_read > 0 {
let message = line.trim();
println!("Mensaje recibido: {}", message);
// Respuesta simple de tipo request-response
let response = format!("Servidor dice: Recibiste '{}'\n", message);
stream.write_all(response.as_bytes())?;
}
// Cierre controlado de la conexión (Lectura y Escritura)
stream.shutdown(Shutdown::Both)?;
println!("Conexión con {} cerrada.", peer_addr);
Ok(())
}
Explicación del Código
El flujo comienza en main con TcpListener::bind, que inicializa el socket en 127.0.0.1:8080. El bucle for stream_result in listener.incoming() itera sobre cada intento de conexión. Usamos un match sobre stream_result para manejar errores de red antes de proceder.
Cuando la conexión es exitosa, client_stream (de tipo TcpStream) se mueve dentro de un hilo mediante thread::spawn(move || ...). El uso de move es indispensable para que el hilo sea el dueño de la variable client_stream.
Dentro de handle_client, aplicamos stream.set_nodelay(true) para optimizar la entrega de paquetes pequeños y stream.set_read_timeout para definir que, si no hay actividad en 5 segundos, la operación de lectura falle.
Para la lógica de negocio, creamos un reader de tipo BufReader usando una referencia a stream (&stream). Esto nos permite usar reader.read_line(&mut line), que llena el buffer line hasta encontrar un salto de línea. Finalmente, utilizamos stream.write_all para enviar una respuesta al cliente y stream.shutdown(Shutdown::Both) para cerrar de manera limpia tanto el canal de lectura como el de escritura antes de que el hilo finalice.
N° 92