UDP (User Datagram Protocol) es un protocolo de la capa de transporte que se caracteriza por ser “sin conexión” (connectionless). A diferencia de TCP, UDP no establece una sesión mediante un saludo de tres vías (handshake), ni garantiza que los datos lleguen, que lleguen en orden o que se entreguen sin errores. El protocolo simplemente empaqueta la información en datagramas y la envía a una dirección de destino.

Este comportamiento lo hace ideal para aplicaciones donde la velocidad y la baja latencia son críticas, como juegos en línea, streaming de video o servicios de DNS, donde perder un pequeño paquete es preferible a esperar por una retransmisión que retrasaría el flujo de datos. Sin embargo, si se usa incorrectamente en aplicaciones que requieren integridad total (como la transferencia de archivos), se corre el riesgo de pérdida de información o corrupción de mensajes, ya que el programador debe implementar manualmente la lógica de retransmisión o ordenamiento si fuera necesario.

Para trabajar con UDP en Rust, se utiliza std::net::UdpSocket. El proceso comienza con UdpSocket::bind, que asocia el socket a una dirección IP y un puerto local. Para enviar datos, se usa send_to, que requiere especificar la dirección del destinatario en cada envío. Por el contrario, recv_from es utilizado para recibir datos, devolviendo tanto la cantidad de bytes leídos como el SocketAddr del remitente. Aunque UDP es sin conexión, existe el método connect; este no establece una sesión real como en TCP, sino que simplemente fija una dirección de destino y origen por defecto, permitiendo usar los métodos más simples send y recv.

use std::net::{UdpSocket, SocketAddr};
use std::thread;
use std::time::Duration;

fn main() -> std::io::Result<()> {
    // 1. Crear un socket para recibir datos en una dirección local
    // El puerto :0 asigna automáticamente un puerto libre disponible
    let socket_receiver = UdpSocket::bind("127.0.0.1:0")?;
    let receiver_addr: SocketAddr = socket_receiver.local_addr()?;
    println!("Servidor UDP escuchando en: {}", receiver_addr);

    // 2. Configurar un timeout de lectura para evitar que el programa se bloquee
    socket_receiver.set_read_timeout(Some(Duration::from_secs(3)))?;

    // 3. Ejecutar la recepción en un hilo separado para permitir la concurrencia
    let receiver_thread = thread::spawn(move || -> std::io::Result<(usize, SocketAddr)> {
        let mut buf = [0u8; 1024];
        // recv_from bloquea hasta recibir datos o alcanzar el timeout
        // Retorna (bytes_leídos, SocketAddr_del_remitente)
        let (len, src) = socket_receiver.recv_from(&mut buf)?;
        println!("Hilo receptor: Recibidos {} bytes de {}", len, src);
        Ok((len, src))
    });

    // 4. Crear un socket para enviar datos
    let socket_sender = UdpSocket::bind("127.0.0.1:0")?;
    let message = b"Mensaje UDP de prueba";

    // 5. Enviar datagramas usando send_to hacia la dirección del receptor
    println!("Enviando mensaje a {}...", receiver_addr);
    socket_sender.send_to(message, receiver_addr)?;

    // 6. Esperar el resultado del hilo receptor
    match receiver_thread.join().unwrap() {
        Ok((len, _src)) => println!("Éxito: Se procesaron {} bytes.", len),
        Err(e) => eprintln!("Error en la recepción: {}", e),
    }

    Ok(())
}

Explicación del Código

  • socket_receiver: Es una instancia de UdpSocket que se encarga de escuchar la red. Se crea mediante UdpSocket::bind utilizando la dirección de bucle de retorno (127.0.0.1) y el puerto 0, lo que permite al sistema operativo asignar un puerto libre.
  • receiver_addr: De tipo SocketAddr, almacena la combinación de la dirección IP y el puerto local asignado al socket_receiver. Es necesaria para que el emisor sepa a dónde enviar el datagrama.
  • socket_receiver.set_read_timeout: Configura un Duration de 3 segundos. Sin esto, si no se recibe ningún paquete, recv_from bloquearía el hilo indefinidamente.
  • buf: Un array de bytes [u8; 1024] que actúa como búfer temporal para almacenar los datos entrantes capturados por recv_from.
  • socket_receiver.recv_from(&mut buf): Este método es fundamental en UDP. A diferencia de TCP, donde ya existe una conexión, aquí cada paquete recibido nos entrega el src (la dirección SocketAddr de quien envió el mensaje), permitiéndonos saber quién nos escribió.
  • socket_sender: Otro UdpSocket independiente utilizado para el envío de datos.
  • socket_sender.send_to(message, receiver_addr): Envía el slice de bytes message directamente a la dirección receiver_addr. No hay validación previa de si el receptor está realmente escuchando.
  • receiver_thread.join(): Espera a que el hilo encargado de la recepción termine su ejecución y devuelve el resultado (Ok con la tupla de bytes y dirección, o Err).

93