socketserver en Python: crea servidores

Publicado el: 19/08/2026
Tempo de leitura: 5 minutos
Cables Ethernet conectados que representan servidores de red con socketserver en Python

El módulo socketserver simplifica las partes repetitivas de un servidor de red. En lugar de crear un socket, hacer bind, escuchar, aceptar clientes y construir manualmente un handler para cada solicitud, eliges una clase de servidor e implementas el método que procesa la petición.

Esta abstracción resulta útil en servicios internos, servidores de prueba, protocolos pequeños, agentes locales y aprendizaje. Aun así, todo servidor necesita framing, límites de tamaño, timeouts, una política de concurrencia, autenticación y apagado predecible. Esta guía cubre TCPServer, UDPServer, StreamRequestHandler, servidores con hilos y límites de producción.

Cómo se organiza socketserver

Las cuatro clases concretas básicas son TCPServer, UDPServer, UnixStreamServer y UnixDatagramServer. Las variantes Unix no están disponibles en todas las plataformas. Por defecto, estas clases procesan una solicitud cada vez.

La concurrencia se añade con ThreadingMixIn o ForkingMixIn. También existen clases listas como ThreadingTCPServer y ThreadingUDPServer. El modelo fork depende de POSIX y separa la memoria; los hilos comparten estado y requieren sincronización.

Antes de avanzar, revisa la guía del cliente TCP con sockets en Python. socketserver organiza el servidor, pero no cambia las propiedades de TCP o UDP.

Primer servidor TCP con StreamRequestHandler

StreamRequestHandler expone rfile y wfile como archivos binarios con buffer. El ejemplo usa un salto de línea como delimitador del mensaje.

import socketserver

MAX_LINE = 10_000

class EchoHandler(socketserver.StreamRequestHandler):
    def handle(self) -> None:
        line = self.rfile.readline(MAX_LINE + 1)

        if len(line) > MAX_LINE:
            self.wfile.write(b"error: mensaje demasiado grande\n")
            return

        if not line.endswith(b"\n"):
            self.wfile.write(b"error: mensaje incompleto\n")
            return

        response = line.rstrip(b"\r\n").upper() + b"\n"
        self.wfile.write(response)

HOST, PORT = "127.0.0.1", 9000

with socketserver.TCPServer((HOST, PORT), EchoHandler) as server:
    server.serve_forever()

El límite de lectura es imprescindible. Sin él, un cliente puede enviar datos indefinidamente sin completar la línea y consumir memoria. Si el protocolo usa texto, define encoding y manejo de errores explícitamente.

TCP es un flujo

Una llamada sendall() del cliente no corresponde necesariamente a una llamada recv() del servidor. Los bytes pueden llegar divididos o agrupados. Cada protocolo debe definir framing: delimitador, prefijo de longitud, registro fijo o cierre de conexión.

La guía de struct y datos binarios en Python muestra cabeceras con longitud explícita. Para protocolos textuales pequeños, una línea limitada suele bastar.

Servidor concurrente con hilos

TCPServer procesa un cliente cada vez. Un cliente lento puede bloquear a los demás. Para conexiones orientadas a I/O, usa ThreadingTCPServer.

class SafeThreadingTCPServer(socketserver.ThreadingTCPServer):
    allow_reuse_address = True
    daemon_threads = False
    block_on_close = True

with SafeThreadingTCPServer((HOST, PORT), EchoHandler) as server:
    server.serve_forever()

Con daemon_threads=False, Python espera a los handlers activos antes de salir. block_on_close=True mantiene una limpieza predecible. Si eliges hilos daemon, documenta que pueden interrumpirse durante el apagado.

La guía de threading en Python explica locks y condiciones de carrera. El GIL no protege automáticamente operaciones compuestas sobre estado compartido.

Estado compartido y locks

Cada petición crea una nueva instancia del handler, pero todas acceden a self.server. Protege actualizaciones compuestas a contadores o cachés.

import threading

class CountingServer(socketserver.ThreadingTCPServer):
    daemon_threads = False

    def __init__(self, address, handler):
        super().__init__(address, handler)
        self.total_requests = 0
        self.counter_lock = threading.Lock()

class CountingHandler(socketserver.StreamRequestHandler):
    def handle(self) -> None:
        with self.server.counter_lock:
            self.server.total_requests += 1
            current = self.server.total_requests

        self.wfile.write(f"solicitud {current}\n".encode("utf-8"))

No mantengas sesiones críticas solo en memoria si habrá procesos, múltiples instancias o reinicios. Usa almacenamiento externo cuando el estado deba sobrevivir o compartirse.

Timeout por conexión

Los clientes lentos pueden ocupar hilos indefinidamente. Configura un timeout en el socket aceptado desde setup().

class TimedHandler(socketserver.StreamRequestHandler):
    timeout_seconds = 10

    def setup(self) -> None:
        super().setup()
        self.request.settimeout(self.timeout_seconds)

    def handle(self) -> None:
        try:
            line = self.rfile.readline(4097)
        except TimeoutError:
            return

        if not line or len(line) > 4096:
            return

        self.wfile.write(b"ok\n")

El atributo server.timeout afecta handle_request(); no impone automáticamente timeout a cada cliente ni al bucle de serve_forever().

Backpressure y cola de conexiones

request_queue_size controla aproximadamente cuántas conexiones esperan mientras el servidor está ocupado. Aumentarlo no limita la concurrencia; solo cambia dónde esperan los clientes.

class LimitedServer(socketserver.ThreadingTCPServer):
    request_queue_size = 32
    allow_reuse_address = True

ThreadingTCPServer crea un hilo por solicitud y no ofrece un pool fijo. Un servicio público necesita límites del sistema, firewall, timeouts, control de conexiones y, a menudo, proxy o arquitectura orientada a eventos.

Filtrar solicitudes

verify_request() permite rechazar un cliente antes del handler.

import ipaddress

ALLOWED = ipaddress.ip_network("10.10.0.0/16")

class InternalServer(socketserver.ThreadingTCPServer):
    def verify_request(self, request, client_address) -> bool:
        client_ip = ipaddress.ip_address(client_address[0])
        return client_ip in ALLOWED

Una allowlist por IP es solo una capa. Los proxies modifican la dirección observada y una red interna puede estar comprometida. Para autenticación fuerte, usa TLS, HMAC o credenciales del protocolo. Consulta la guía de ssl y TLS en Python.

Servidor UDP

UDP procesa datagramas independientes que pueden perderse, duplicarse o llegar desordenados.

class UDPHandler(socketserver.BaseRequestHandler):
    def handle(self) -> None:
        data, udp_socket = self.request

        if len(data) > 1024:
            return

        response = data.strip().upper()
        udp_socket.sendto(response, self.client_address)

with socketserver.ThreadingUDPServer((HOST, 9001), UDPHandler) as server:
    server.serve_forever()

No uses la dirección de origen como autenticación. Evita respuestas mucho mayores que la petición para prevenir amplificación. Los protocolos importantes necesitan IDs y protección contra replay.

Manejo de errores

Por defecto, una excepción en handle() imprime un traceback y el servidor continúa. Sobrescribe handle_error() para logs estructurados sin exponer payloads.

import logging
logger = logging.getLogger(__name__)

class LoggedServer(socketserver.ThreadingTCPServer):
    def handle_error(self, request, client_address) -> None:
        logger.exception(
            "fallo al procesar cliente",
            extra={"client_ip": client_address[0]},
        )

Registra metadatos e identificadores de correlación, no tokens ni cuerpos completos.

Apagado correcto

serve_forever() se detiene tras llamar a shutdown(). Esa llamada debe realizarse desde otro hilo; hacerlo desde el mismo hilo del bucle produce deadlock.

import threading

server = SafeThreadingTCPServer((HOST, PORT), EchoHandler)
thread = threading.Thread(target=server.serve_forever)
thread.start()

try:
    thread.join()
except KeyboardInterrupt:
    server.shutdown()
    server.server_close()
    thread.join()

En producción, deja de aceptar trabajo, espera las solicitudes activas hasta un plazo y libera los recursos.

IPv6

import socket

class IPv6TCPServer(socketserver.ThreadingTCPServer):
    address_family = socket.AF_INET6

El comportamiento dual stack varía entre sistemas. Prueba direcciones IPv4 mapeadas y normaliza los clientes antes de aplicar políticas.

Cuándo usar socketserver

Es apropiado para protocolos pequeños, herramientas internas, agentes locales, mocks y aprendizaje. Para HTTP público usa un framework y servidor WSGI o ASGI. Para miles de conexiones duraderas, considera asyncio. Para CPU intensiva, procesos limitados y colas externas ofrecen mejor aislamiento.

Errores frecuentes

Los fallos habituales son leer sin límite, asumir que un receive equivale a un mensaje, usar el servidor síncrono con clientes lentos, modificar estado sin lock, crear hilos ilimitados, omitir timeouts, confiar solo en IP, llamar shutdown() desde el hilo incorrecto y exponer un servidor de demostración a internet.

Buenas prácticas

Define framing, tamaños máximos y timeouts. Elige conscientemente entre sincronía, hilos, procesos o event loop. Protege estado compartido. Añade TLS y autenticación. Implementa logs seguros, métricas y apagado probado. Coloca servicios públicos detrás de controles y límites adicionales.

Conclusión

socketserver elimina mucho código repetitivo de servidores TCP y UDP, pero no elimina las responsabilidades del protocolo. Framing, límites, concurrencia, autenticación y apagado siguen siendo esenciales. Es claro y eficaz para servicios controlados; cargas públicas necesitan pools, event loops e infraestructura de producción.

Consulta la documentación oficial de socketserver y el RFC 9293 sobre TCP.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Archivos protegidos que representan extracción segura de TAR con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extrae TAR con seguridad

    Aprende tarfile extraction_filter en Python para extraer archivos TAR con validación, seguridad y control de rutas.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python mostrando avisos controlados con catch_warnings
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: captura warnings en pruebas Python

    Aprende catch_warnings en Python para capturar, probar y controlar avisos con filtros específicos y alcance seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python que representa referencias persistentes de pickle
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serializa referencias externas

    Aprende pickle persistent_id en Python para referencias externas estables, validación, seguridad, rendimiento y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binario que representa buffers y vistas de memoria en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: cuenta valores sin copiar buffers

    Aprende memoryview.count en Python para contar bytes y valores en buffers sin copias, con formatos, límites y buenas prácticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python y anotaciones de tipos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evita imports circulares en anotaciones

    Aprende annotationlib en Python para inspeccionar anotaciones diferidas, evitar imports circulares y crear herramientas seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026