En el nivel de la capa de transporte del modelo OSI, existen dos protocolos fundamentales que rigen el intercambio de información: el Protocolo de Datagramas de Usuario (UDP) y el Protocolo de Control de Transmisión (TCP). Aunque ambos operan en la misma capa, sus mecanismos de entrega y fiabilidad presentan diferencias arquitectónicas significativas.
Protocolo UDP (User Datagram Protocol)
UDP es un protocolo no orientado a la conexión que permite el envío de datos sin necesidad de establecer un canal previo entre el emisor y el receptor. Se caracteriza por priorizar la velocidad sobre la integridad total de los paquetes.
- Ausencia de conexión: No existe una fase de negociación inicial. El emisor simplemente encapsula los datos con una cabecera UDP y los envía a la red.
- Capacidad de difusión: A diferencia de otros protocolos, UDP soporta transmisiones de tipo unicast, multicast y broadcast, permitiendo enviar datos a múltiples receptores simultáneamente.
- Orientación a mensajes: UDP respeta los límites de los mensajes enviados por la aplicación. No fragmenta ni agrupa los datos por sí mismo, manteniendo la estructura original del datagrama.
- Baja latencia y falta de fiabilidad: Al no contar con mecanismos de control de congestión ni confirmación de recepción (ACK), los datos se envían a un ritmo constante. Si ocurre una pérdida en la red, UDP no intenta retransmitir.
- Cabecera ligera: La estructura de su cabecera es de apenas 8 bytes, compuesta por el puerto de origen, puerto de destino, longitud y suma de verificación (checksum).
Protocolo TCP (Transmission Control Protocol)
TCP es un protocolo orientado a la conexión que garantiza que los datos lleguen de forma íntegra, en orden y sin errores al destinatario.
- Establecimiento de conexión: Utiliza el proceso de "negociación en tres pasos" (three-way handshake) para sincronizar al cliente y al servidor antes de cualquier transferencia.
- Transmisión punto a punto: Solo permite la comunicación entre dos extremos (unicast).
- Flujo de bytes: A diferencia de UDP, TCP ve los datos como un flujo continuo de bytes. No preserva automáticamente los límites de los mensajes de la capa de aplicación.
- Transferencia fiable: Implementa números de secuencia y acuses de recibo. Si un paquete se pierde o llega dañado, el protocolo gestiona automáticamente su retransmisión.
- Control de congestión: Ajusta dinámicamente la velocidad de envío basándose en el estado de saturación de la red para evitar colapsos.
Comparativa Técnica
| Característica | UDP | TCP |
|---|---|---|
| Conexión | Sin conexión | Orientado a la conexión |
| Fiabilidad | No garantizada | Garantizada (control de errores) |
| Destinatarios | Unicast, Multicast, Broadcast | Unicast únicamente |
| Modo de datos | Basado en datagramas (mensajes) | Basado en flujo de bytes |
| Velocidad | Alta eficiencia / Rápido | Mayor sobrecarga / Lento |
| Tamaño Cabecera | 8 bytes fijo | 20 a 60 bytes |
Gestión de Mensajes en TCP: Fragmentación y Concatenación
Debido a que TCP es un flujo de bytes, en ocasiones los mensajes pueden "pegarse" (concatenación) o dividirse si el tamaño supera la Unidad Máxima de Transmisión (MTU). Esto ocurre principalmente por:
- El tamaño de los datos es mayor que el espacio disponible en el búfer de envío.
- Múltiples mensajes pequeños se agrupan en un solo segmento para optimizar la red (Algoritmo de Nagle).
Para solucionar esto, los desarrolladores suelen implementar:
- Delimitadores: Insertar caracteres especiales (como \n o \0) para separar mensajes.
- Longitud en cabecera: Anteponer a cada mensaje unos bytes que indiquen el tamaño total del cuerpo del mensaje.
- Tamaño fijo: Rellenar todos los mensajes para que tengan siempre la misma longitud.
Implementación con Python Sockets
Servidor TCP
import socket
def iniciar_servidor_tcp():
# Creación del socket de flujo (TCP)
svr = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
svr.bind(('127.0.0.1', 8500))
svr.listen(5)
print("Servidor TCP a la escucha...")
while True:
canal, direccion = svr.accept()
print(f"Conexión establecida desde: {direccion}")
info_recibida = canal.recv(1024)
if info_recibida:
print(f"Mensaje: {info_recibida.decode('utf-8')}")
canal.send("ACK: Datos recibidos".encode('utf-8'))
canal.close()
if __name__ == "__main__":
iniciar_servidor_tcp()
Cliente TCP
import socket
def ejecutar_cliente_tcp():
cliente = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cliente.connect(('127.0.0.1', 8500))
msg = "Hola servidor, solicito datos."
cliente.sendall(msg.encode('utf-8'))
respuesta = cliente.recv(1024)
print(f"Respuesta servidor: {respuesta.decode('utf-8')}")
cliente.close()
if __name__ == "__main__":
ejecutar_cliente_tcp()
Servidor UDP
import socket
def servidor_udp_basico():
# Socket de datagramas (UDP)
sock_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_udp.bind(('127.0.0.1', 9500))
print("Escuchando peticiones UDP...")
while True:
contenido, remitente = sock_udp.recvfrom(1024)
print(f"Desde {remitente}: {contenido.decode('utf-8')}")
sock_udp.sendto(b"Recibido", remitente)
if __name__ == "__main__":
servidor_udp_basico()
Cliente UDP
import socket
def cliente_udp_simple():
host_remoto = ('127.0.0.1', 9500)
cliente_u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
texto = input("Escriba un mensaje (o 'exit'): ")
if texto == 'exit': break
cliente_u.sendto(texto.encode('utf-8'), host_remoto)
data, _ = cliente_u.recvfrom(1024)
print(f"Confirmación: {data.decode('utf-8')}")
cliente_u.close()
if __name__ == "__main__":
cliente_udp_simple()