El módulo socket proporciona la interfaz de bajo nivel para comunicación de red en Python. Permite crear clientes y servidores TCP, intercambiar datagramas UDP, trabajar con IPv4, IPv6 y sockets locales, configurar timeouts, resolver nombres y acceder a opciones del sistema operativo. Bibliotecas de HTTP, SMTP, bases de datos y mensajería se construyen sobre conceptos de transporte similares.
Los sockets transportan bytes, no mensajes Python. La aplicación debe definir framing, encoding, límites, deadlines, autenticación y manejo de desconexión. Para un protocolo estándar, prefiere una biblioteca madura de alto nivel. Usa socket al implementar o estudiar un protocolo, integrar un servicio especializado o necesitar control detallado.
Familias de direcciones y tipos
AF_INET representa IPv4, AF_INET6 representa IPv6 y AF_UNIX, cuando existe, ofrece comunicación local por pathname. TCP normalmente usa SOCK_STREAM; UDP utiliza SOCK_DGRAM.
import socket
cliente = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
cliente.settimeout(5)
cliente.connect(("example.com", 80))
finally:
cliente.close()
Prefiere un context manager para cerrar el descriptor incluso cuando ocurre una excepción.
Conecta con create_connection
socket.create_connection() resuelve el host e intenta direcciones adecuadas, simplificando clientes TCP.
import socket
with socket.create_connection(("example.com", 80), timeout=5) as sock:
sock.sendall(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
respuesta = sock.recv(4096)
print(respuesta[:100])
Para HTTP de producción, utiliza una biblioteca HTTP. El ejemplo muestra únicamente la capa de transporte.
TCP es un flujo de bytes
TCP entrega un flujo ordenado. Una llamada a send() en un lado no corresponde necesariamente a una llamada a recv() en el otro. Los datos pueden llegar divididos o agrupados.
El protocolo de aplicación debe indicar dónde termina cada mensaje mediante delimitador validado, header fijo, prefijo de longitud o formato autodescriptivo.
send y sendall
send() puede transmitir solo parte del buffer y devuelve la cantidad aceptada. sendall() continúa hasta enviar todo o encontrar un error.
mensaje = b"datos importantes"
sock.sendall(mensaje)
Incluso después de sendall(), el receptor debe reconstruir el mensaje. El éxito significa que el sistema local aceptó los bytes, no que la aplicación remota los procesó.
recv y cierre ordenado
recv(tamano) devuelve hasta la cantidad solicitada. Un bytes vacío indica que el peer cerró su dirección de envío correctamente.
partes = []
while True:
bloque = sock.recv(4096)
if not bloque:
break
partes.append(bloque)
datos = b"".join(partes)
No esperes EOF cuando el protocolo mantiene la conexión abierta para múltiples mensajes; usa framing.
Framing con prefijo de longitud
Un diseño común envía primero un tamaño fijo y después el payload.
import struct
def recibir_exacto(sock, cantidad):
partes = []
restante = cantidad
while restante:
bloque = sock.recv(restante)
if not bloque:
raise ConnectionError("conexión cerrada")
partes.append(bloque)
restante -= len(bloque)
return b"".join(partes)
header = recibir_exacto(sock, 4)
tamano = struct.unpack("!I", header)[0]
if tamano > 1_000_000:
raise ValueError("mensaje demasiado grande")
payload = recibir_exacto(sock, tamano)
Valida el tamaño declarado antes de reservar memoria.
Servidor TCP básico
Un servidor crea el socket, asocia una dirección, escucha y acepta conexiones.
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as servidor:
servidor.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
servidor.bind(("127.0.0.1", 9000))
servidor.listen()
conexion, direccion = servidor.accept()
with conexion:
conexion.sendall(b"hola\n")
Asociar a 127.0.0.1 restringe acceso a la máquina local. Escuchar en todas las interfaces expone el servicio y exige autenticación, firewall, límites y hardening.
listen y backlog
listen() coloca el socket en modo pasivo. El backlog influye en la cola de conexiones pendientes, aunque el comportamiento exacto depende del sistema.
Un backlog grande no arregla un servidor lento. Acepta rápidamente, limita clientes y configura timeouts.
accept devuelve otro socket
accept() devuelve un nuevo socket conectado y la dirección remota. El socket de escucha sigue disponible.
Cierra ambos de forma independiente. Filtrar conexiones aceptadas agota descriptores.
Concurrencia del servidor
Un servidor mínimo atiende un cliente por vez. Para varios clientes, usa threads, procesos, selectors o asyncio. La elección depende de cantidad de conexiones, workload, latencia y complejidad.
select en Python explica multiplexación de descriptores. No crees una thread ilimitada por conexión.
Timeouts
settimeout(segundos) limita operaciones bloqueantes.
sock.settimeout(10)
try:
datos = sock.recv(4096)
except socket.timeout:
registrar_aviso("cliente inactivo")
Define deadlines de conexión, lectura y escritura según el protocolo. Una conexión sin límite puede ocupar un worker indefinidamente.
Modo no bloqueante
setblocking(False) hace que las llamadas vuelvan inmediatamente y lancen BlockingIOError cuando todavía no pueden avanzar.
No construyas un busy loop. Usa selectors, select o un event loop.
UDP
UDP envía datagramas independientes sin entrega fiable, orden, retransmisión ni estado de conexión.
import socket
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.settimeout(2)
sock.sendto(b"consulta", ("127.0.0.1", 9999))
datos, origen = sock.recvfrom(2048)
print(origen, datos)
Los datagramas pueden perderse, duplicarse, reordenarse o truncarse si el buffer es pequeño. La aplicación define retries e idempotencia.
Tamaño de UDP
Aunque UDP puede representar datagramas grandes, las redes reales tienen MTU y fragmentación. Los mensajes pequeños son más robustos. Usa TCP u otro protocolo para payloads grandes o fiables.
Resolución de nombres
getaddrinfo() resuelve un hostname y devuelve alternativas con familia, tipo, protocolo y dirección.
for resultado in socket.getaddrinfo("example.com", 443, type=socket.SOCK_STREAM):
familia, tipo, protocolo, canonico, direccion = resultado
print(familia, direccion)
DNS puede ser lento, fallar o devolver varias direcciones. No guardes una respuesta para siempre sin política.
IPv4 e IPv6
Usa getaddrinfo() e intenta las direcciones devueltas en lugar de asumir IPv4. Las tuplas IPv6 difieren y el dual stack puede requerir configuración específica.
Prueba despliegues IPv4-only, IPv6-only y dual stack.
Puertos y orden de bytes
Los puertos van de 0 a 65535 y los bajos pueden requerir privilegios. Funciones como htons(), htonl(), ntohs() y ntohl() convierten orden de bytes. Los protocolos binarios suelen usar struct con prefijo !.
SO_REUSEADDR
SO_REUSEADDR puede facilitar reinicio de servidores cuando quedan conexiones cerrándose. Su semántica varía y no significa que varios servidores independientes deban compartir una puerta.
Configura opciones antes de bind().
Keepalive
SO_KEEPALIVE habilita probes del sistema para detectar conexiones rotas, pero los intervalos por defecto pueden ser largos y el tuning es específico de plataforma.
Keepalive no sustituye heartbeats y deadlines de aplicación.
shutdown
shutdown(socket.SHUT_WR) indica que el programa no enviará más datos mientras puede seguir recibiendo. close() libera el descriptor local.
Usa half-close solo cuando el protocolo defina su significado.
TLS
Un socket TCP plano no ofrece confidencialidad, autenticación del peer ni protección frente a intermediarios. Envuélvelo con un ssl.SSLContext correctamente configurado o utiliza un cliente seguro de alto nivel.
No inventes criptografía ni desactives verificación de certificados como solución permanente.
Encoding
La red transporta bytes. Define el encoding de texto explícitamente.
mensaje = "hola".encode("utf-8")
sock.sendall(mensaje)
texto = datos.decode("utf-8", errors="strict")
Los delimitadores se definen como bytes y las secuencias inválidas necesitan una política.
Seguridad de entrada
Limita tamaño, profundidad, cantidad de campos y tiempo de procesamiento. Nunca pases datos de red directamente a eval(), shell, SQL o deserializadores inseguros.
Autentica clientes cuando sea necesario y autoriza cada operación.
Clientes lentos
Un cliente puede enviar pocos bytes lentamente y ocupar un worker. Usa deadlines, progreso mínimo, límites de conexiones y multiplexación.
Rate limiting y backpressure deben proteger memoria y servicios downstream.
Fallos transitorios
Las conexiones fallan por DNS, timeout, reset, routing, refusal o indisponibilidad temporal. Reintenta solo operaciones idempotentes, con límite y backoff exponencial.
No repitas indefinidamente ni trates un fallo de autenticación como transitorio.
Sockets locales Unix
Cuando AF_UNIX está disponible, procesos de una máquina pueden comunicarse mediante pathname con menor exposición a la red.
Gestiona permisos, elimina paths antiguos con cuidado y restringe el directorio.
Pruebas
Prueba frames divididos, varios frames juntos, EOF, timeout, reset, direcciones inválidas, IPv6, DNS múltiple, clientes lentos, payload máximo, conexiones simultáneas, shutdown y pérdida de red.
Usa sockets locales y puertos efímeros. Evita depender de servicios públicos.
Observabilidad
Registra dirección, duración, bytes, estado del protocolo y categoría de error sin guardar secretos o payloads sensibles. Métricas de conexiones activas, timeouts, resets y colas muestran saturación.
Errores comunes
Los fallos frecuentes son suponer que recv() devuelve un mensaje completo, ignorar envíos parciales, omitir timeouts, exponer el servidor accidentalmente, esperar fiabilidad de UDP, asignar una thread ilimitada por cliente, tratar DNS como identidad y enviar datos sensibles sin TLS.
Conclusión
socket ofrece control directo de red, pero exige un protocolo explícito. Define framing, límites, timeouts, seguridad, lifecycle y concurrencia antes de desplegar.
Consulta la documentación oficial de socket y select en Python para multiplexar conexiones.







