Implementación de Comunicación en Red con Python: Protocolos TCP y UDP

La programación de redes permite la comunicación entre diferentes procesos que se ejecutan en distintas máquinas conectadas a través de una red. En Python, el módulo fundamental para realizar estas operaciones es socket, que proporciona una interfaz para interactuar con los protocolos de transporte TCP y UDP.

Protocolo TCP (Transmission Control Protocol)

TCP es un protocolo orientado a la conexión, conocido por ser fiable y basado en flujos de datos. Su fiabilidad reside en el mecanismo de retroalimentación: por cada paquete enviado, se espera una confirmación antes de continuar, garantizando que los datos lleguen íntegros y en orden.

Comunicación Básica con TCP

A continuación, se presenta un ejemplo de un servidor de eco básico y su corrrespondiente cliente.

Servidor TCP:

import socket

# Inicialización del socket orientado a flujo
servidor_red = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
servidor_red.bind(('127.0.0.1', 9000))
servidor_red.listen(10)

print("Servidor activo, esperando conexiones...")

while True:
    canal, direccion = servidor_red.accept()
    print(f"Conexión establecida con: {direccion}")
    
    try:
        while True:
            peticion = canal.recv(1024)
            if not peticion:
                break
            
            # Procesamiento simple: convertir a mayúsculas
            respuesta = peticion.decode('utf-8').upper().encode('utf-8')
            canal.send(respuesta)
    except ConnectionResetError:
        print("El cliente cerró la conexión abruptamente.")
    finally:
        canal.close()

Cliente TCP:

import socket

emisor = socket.socket()
emisor.connect(('127.0.0.1', 9000))

while True:
    entrada = input("Escriba su mensaje (o 'exit'): ").strip()
    if not entrada: continue
    if entrada.lower() == 'exit': break
    
    emisor.send(entrada.encode('utf-8'))
    eco = emisor.recv(1024)
    print(f"Respuesta del servidor: {eco.decode('utf-8')}")

emisor.close()

Gestión de Flujos y el Problema de la "Pegajosidad" de Paquetes

Dado que TCP es un flujo de bytes continuo, el receptor puede no saber dónde termina un mensaje y empieza otro si se envían rápidamente. Para solucionar esto, se utilizan cabeceras de longitud fija mediante el módulo struct.

import struct
import json

# Empaquetado de metadatos
metadata = {'filename': 'video_proy.mp4', 'filesize': 1048576}
json_data = json.dumps(metadata).encode('utf-8')

# Creamos una cabecera de 4 bytes que indica la longitud del JSON
cabecera = struct.pack('i', len(json_data))

# El protocolo de envío sería: [Cabecera 4 bytes] + [JSON con info] + [Datos del archivo]
print(f"Tamaño de la cabecera: {len(cabecera)} bytes")

Serialización Personalizada con JSON

El módulo json estándar no puede procesar objetos de tipo datetime de forma nativa. Para solucionar esto en aplicaciones de red que requieren marcas de tiempo, podemos extender la clase JSONEncoder.

import json
from datetime import datetime

class AdaptadorJSON(json.JSONEncoder):
    def default(self, objeto):
        if isinstance(objeto, datetime):
            return objeto.strftime('%Y-%m-%d %H:%M:%S')
        return super().default(objeto)

registro = {
    "evento": "inicio_transferencia",
    "timestamp": datetime.now()
}

json_serializado = json.dumps(registro, cls=AdaptadorJSON)
print(f"Datos serializados: {json_serializado}")

Ejecución de Comandos Mediante Subprocess

En aplicaciones cliente-servidor de administración remota, es común querer ejecutar comandos del sistema y devolver el resultado a través del socket.

import subprocess

def ejecutar_comando(cmd_string):
    proceso = subprocess.Popen(
        cmd_string, 
        shell=True, 
        stdout=subprocess.PIPE, 
        stderr=subprocess.PIPE
    )
    salida, error = proceso.communicate()
    return salida.decode('gbk') if salida else error.decode('gbk')

# Ejemplo de uso
resultado = ejecutar_comando('dir')
print(resultado)

Protocolo UDP (User Datagram Protocol)

A diferencia de TCP, UDP no establece una conexión bidireccional previa. Es un protocolo de "dispaarr y olvidar" (best-effort), lo que lo hace mucho más rápido pero menos fiable.

Servidor UDP (Ejemplo de Mansajería)

import socket

# SOCK_DGRAM indica que usamos UDP
nodo_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
nodo_udp.bind(('127.0.0.1', 9001))

print("Servidor UDP escuchando...")

while True:
    datos, addr_remoto = nodo_udp.recvfrom(2048)
    print(f"Mensaje de {addr_remoto}: {datos.decode('utf-8')}")
    
    respuesta = f"Recibido: {datos.decode('utf-8')}".encode('utf-8')
    nodo_udp.sendto(respuesta, addr_remoto)

Cliente UDP

import socket

gestor_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
destino = ('127.0.0.1', 9001)

while True:
    msg = input("Mensaje UDP: ").encode('utf-8')
    gestor_udp.sendto(msg, destino)
    
    datos_vuelta, server_addr = gestor_udp.recvfrom(2048)
    print(f"Confirmación: {datos_vuelta.decode('utf-8')}")

Comparativa Técnica: TCP vs UDP

Característica TCP UDP
Conexión Orientado a conexión (Handshake) Sin conexión
Fiabilidad Alta (confirmación y retransmisión) Baja (pérdida de datos posible)
Orden Garantiza el orden de llegada No garantiza el orden
Velocidad Más lento por sobrecarga de control Muy rápido
Límites Flujo continuo (riesgo de sticky packets) Basado en datagramas independientes

Etiquetas: Python sockets tcp UDP network-programming

Publicado el 7-26 14:43