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 |