miércoles, 2 de septiembre de 2026

Programación con Sockets TCP y UDP.

 En los sistemas distribuidos, al no existir una memoria física compartida entre los distintos nodos, los procesos se coordinan e interactúan exclusivamente mediante el paso de mensajes a través de la red física. Para ocultar los detalles complejos del encapsulamiento de red y las tramas físicas, los sistemas operativos ofrecen la abstracción de Socket
  • .

    1. La Abstracción de Sockets y la Capa de Transporte
    • Definición de Socket: Un Socket (o punto final de comunicación / endpoint) es una abstracción de software del sistema operativo que actúa como una interfaz de Entrada/Salida (E/S) a través de la cual los procesos pueden enviar y recibir datos en una red de computadoras
  • .
  • Identificación del Endpoint: Un socket se identifica unívocamente en la red mediante una tupla de 5 elementos (conocida como 5-tuple)
  • :
  • Puertos: Los sockets utilizan puertos para direccionar los procesos
  • . Los puertos bien conocidos (Well-known Ports) van del 0 al 1023 (por ejemplo, HTTP en el puerto 80, HTTPS en el 443 o SSH en el 22)
    • .

    2. Comparativa Teórica: Sockets TCP vs. Sockets UDP
    La API de sockets estándar ofrece dos abstracciones principales según el comportamiento y los protocolos requeridos en la capa de transporte:
                          ┌──────────────────────────────────────────┐
                          │     SOCKETS EN LA CAPA DE TRANSPORTE     │
                          └────────────────────┬─────────────────────┘
                                               │
                 ┌─────────────────────────────┴─────────────────────────────┐
                 ▼                                                           ▼
    ┌───────────────────────────┐                               ┌───────────────────────────┐
    │        SOCKETS TCP        │                               │        SOCKETS UDP        │
    │    (Stream-Oriented)      │                               │    (Datagram-Oriented)    │
    ├───────────────────────────┤                               ├───────────────────────────┤
    │ • Orientado a Conexión    │                               │ • Sin Conexión (Connectionless)
    │ • Flujo de Bytes Continuo │                               │ • Conserva Límites (Datagrama)
    │ • Control de Flujo/Congestión                             │ • Sin Garantía de Entrega │
    │ • Fiabilidad (ACKs/Retrans)                               │ • Mínima Sobrecarga (Low Latency)
    └───────────────────────────┘                               └───────────────────────────┘
    
    A. Sockets TCP (java.net.Socket / java.net.ServerSocket)
    1. Orientación a conexión: Antes de transmitir datos, los sockets cliente y servidor deben ejecutar una negociación de 3 vías (Three-way Handshake: SYN, SYN-ACK, ACK) para establecer la conexión
  • .
  • Abstracción de Flujo (Byte Stream): Los datos se transmiten como una secuencia ininterrumpida de bytes
  • . El receptor no distingue los límites de los mensajes individuales escritos por el emisor, simplemente lee de un búfer de entrada continuo
  • .
  • Fiabilidad y Garantía de Orden: TCP garantiza que los datos lleguen en orden y sin pérdidas
  • . Para ello, asigna un número de secuencia a cada byte. Si un paquete se pierde en la red (fallo de omisión), el receptor no envía el acuse de recibo (ACK), lo que obliga al emisor a retransmitir el paquete
  • .
  • Uso típico: Transferencia de archivos, servicios de mensajería crítica y protocolos donde la integridad de la información es vital
    1. .
    B. Sockets UDP (java.net.DatagramSocket / java.net.DatagramPacket)
    1. Sin Conexión (Connectionless): No requiere negociación ni establecimiento de enlace previo
    . Los datagramas se envían directamente especificando la dirección IP y el puerto de destino en cada paquete individual
  • .
  • Preservación de Límites de Datagrama: A diferencia de TCP, en UDP se preservan las fronteras del mensaje
  • . Una operación de lectura extrae exactamente los bytes que fueron empaquetados en la operación de envío correspondiente
  • .
  • Modelo de Fallo (Sin garantías): Asume un modelo de fallo por omisión
  • . Si un router descarta un datagrama por congestión o pérdida, UDP no realiza retransmisiones ni notifica al emisor, y no se garantiza el orden de llegada
  • .
  • Uso típico: Streaming de audio o video en tiempo real, juegos en línea o consultas DNS, donde se prioriza la baja latencia sobre la fiabilidad
    1. .

    3. Flujo de Interacción de Sockets
    Diagrama de Sockets UDP (Sin Conexión)
    Los procesos emisor y receptor interactúan de manera directa enviando paquetes aislados mediante sockets UDP
    :
            PROCESO SERVIDOR                                    PROCESO CLIENTE
         ┌──────────────────────┐                            ┌──────────────────────┐
         │  socket() (Crear)    │                            │  socket() (Crear)    │
         └──────────┬───────────┘                            └──────────┬───────────┘
                    │                                                   │
         ┌──────────▼───────────┐                            ┌──────────▼───────────┐
         │   bind() (Enlazar    │                            │   bind() (Puerto     │
         │   Puerto Conocido)   │                            │  Efémero / Opcional) │
         └──────────┬───────────┘                            └──────────┬───────────┘
                    │                                                   │
         ┌──────────▼───────────┐     Mensaje en Red          ┌──────────▼───────────┐
         │  recvfrom() (Espera  │ <───────────────────────── │  sendto() (Envía     │
         │   Datagrama)         │                            │  Datagrama)          │
         └──────────┬───────────┘                            └──────────┬───────────┘
                    │                                                   │
                    │                 Mensaje en Red                    │
                    │ ─────────────────────────────────────────> │
         ┌──────────▼───────────┐                            ┌──────────▼───────────┐
         │  sendto() (Responde) │                            │  recvfrom() (Espera  │
         └──────────┬───────────┘                            │  Respuesta)          │
                    │                                        └──────────┬───────────┘
         ┌──────────▼───────────┐                            ┌──────────▼───────────┐
         │  close() (Cerrar)    │                            │  close() (Cerrar)    │
         └──────────────────────┘                            └──────────────────────┘
    
    Diagrama de Sockets TCP (Orientado a Conexión Multi-hilo)
    En TCP, el servidor debe gestionar el establecimiento de la conexión y delegar la atención del cliente para no bloquear el puerto principal
    :
           PROCESO SERVIDOR                                       PROCESO CLIENTE
         ┌──────────────────────┐                            ┌──────────────────────┐
         │ ServerSocket(Puerto) │                            │  Socket(IP, Puerto)  │
         └──────────┬───────────┘                            └──────────┬───────────┘
                    │                                                   │
         ┌──────────▼───────────┐       Handshake TCP            ┌──────────▼───────────┐
         │ accept() (Bloqueante)│ <─────────────────────────>│ connect() (Inicia    │
         └──────────┬───────────┘ (SYN -> SYN-ACK -> ACK)    │  Conexión)           │
                    │                                        └──────────┬───────────┘
         ┌──────────▼───────────┐                                       │
         │ Genera SocketCliente │                                       │
         │ Dedicado             │                                       │
         └──────────┬───────────┘                                       │
                    │                                                   │
         ┌──────────▼───────────┐    Canal TCP de Bytes      ┌──────────▼───────────┐
         │ Crea Hilo Hijo       │ <────────────────────────> │ InputStream /        │
         │ (ManejadorCliente)   │ (InputStream/OutputStream) │ OutputStream         │
         └──────────────────────┘                            └──────────┬───────────┘
                    │                                                   │
         ┌──────────▼───────────┐                            ┌──────────▼───────────┐
         │ Retorna a accept()   │                            │ close()              │
         │ para nuevo cliente   │                            └──────────────────────┘
         └──────────────────────┘
    

    4. Concurrencia y Servidores Multihilo
    • El método accept() en un ServerSocket TCP es una operación bloqueante por definición
    . Si un servidor se programa de forma secuencial (monohilo), quedará bloqueado atendiendo los flujos de lectura y escritura del primer cliente que se conecte, impidiendo que otros puedan establecer una conexión
  • .
  • El Patrón Multihilo: Para lograr concurrencia real y un mayor rendimiento (throughput), el hilo principal del servidor (main) debe limitarse estrictamente a escuchar y aceptar conexiones activas en un bucle continuo (while(true))
  • .
  • Delegación Dinámica: Cada vez que accept() retorna un nuevo objeto Socket exclusivo para el cliente entrante, el servidor delega la tarea instanciando un hilo de ejecución independiente (usando un nuevo Thread con un Runnable o un pool de hilos de tipo ExecutorService)
  • .
  • Liberación de Recursos: El hilo secundario se encarga de la comunicación directa (lectura y escritura secuencial) con su cliente asignado, lo que permite que el hilo principal regrese inmediatamente a esperar nuevas solicitudes en accept()
  • . Al cerrarse la conexión, se liberan los sockets y flujos asociados de forma segura.

En un programa o sistema distribuido, la decisión de usar TCP o UDP se define en dos niveles distintos: a nivel de código (en la implementación de la API de red) y a nivel de diseño arquitectónico.

1. ¿Dónde se define en el Código? (Nivel de Implementación en Java)

En tu código, se define explícitamente en el momento en que instancias y utilizas las clases de la API de red (java.net):

A. Si eliges TCP (Orientado a Conexión y Streams):

Defines el uso de TCP al importar java.net.* e instanciar las clases específicas para flujos continuos (streams):

  • En el Servidor: Instancias ServerSocket.

  • En el Cliente: Instancias Socket.

  • Intercambio: Utilizas flujos de entrada y salida (InputStream y OutputStream / BufferedReader y PrintWriter) para enviar/recibir datos.

Java
// AL INSTANCIAR ServerSocket / Socket DEFINES QUE USAS TCP
ServerSocket servidor = new ServerSocket(5000); //
Socket socketCliente = servidor.accept();       //

B. Si eliges UDP (Sin Conexión y Datagramas):

Defines el uso de UDP al instanciar las clases específicas para paquetes independientes (datagramas):

  • Tanto en Cliente como Servidor: Instancias DatagramSocket.

  • Intercambio: Empaquetas los bytes de datos junto a la IP y puerto de destino en objetos DatagramPacket.

  • Envío/Recepción: Llamas a los métodos .send(paquete) y .receive(paquete).

Java
// AL INSTANCIAR DatagramSocket / DatagramPacket DEFINES QUE USAS UDP
DatagramSocket socketUDP = new DatagramSocket(6000); //
DatagramPacket paquete = new DatagramPacket(buffer, buffer.length); //
socketUDP.receive(paquete); //

2. ¿Dónde se define en el Diseño? (Criterio Técnico)

Antes de escribir la primera línea de código, la elección de TCP o UDP la defines según las necesidades de tu aplicación:

Criterio de SelecciónUsas TCP cuando...Usas UDP cuando...
Fiabilidad y Orden
Necesitas garantía de entrega: No te puedes permitir perder ni un solo byte de datos (ej. transferencia de archivos, transacciones, mensajes de chat).

Toleras pérdida ocasional de paquetes: Es más importante que el dato llegue rápido a que llegue completo (ej. streaming de video, voz sobre IP, videojuegos online)[cite: 1].

Sobrecarga (Overhead)
Aceptas la sobrecarga (overhead) del three-way handshake (establecimiento de conexión) y mensajes de confirmación (ACKs)[cite: 1].

Quieres evitar la sobrecarga de control y minimizar la latencia enviando paquetes directamente[cite: 1].

Control de Flujo
Requieres que la red gestione el control de flujo para no desbordar al receptor[cite: 1].

Quieres control total del envío de mensajes independientes[cite: 1].

3. Casos Especiales en Arquitecturas Distribuidas

  • Middleware y RMI / gRPC: Al utilizar librerías de más alto nivel (como Java RMI, gRPC o WebSockets), la librería interna o el framework ya definió por ti el protocolo subyacente (generalmente TCP o HTTP/2 sobre TCP para garantizar que las invocaciones de métodos remotos y serialización de objetos no se pierdan).

  • Servidores Híbridos: Tal como se analiza en la materia, un mismo servidor puede soportar múltiples protocolos al mismo tiempo en puertos separados: recibir datos continuos mediante una conexión TCP y recibir alertas o métricas rápidas mediante datagramas UDP en paralelo.

No hay comentarios:

Publicar un comentario