selectors en Python: varios sockets

Publicado el: 28/08/2026
Tempo de leitura: 5 minutos
Flat lay of a complete toolset neatly organized in a workshop setting, essential for auto repair tasks.

El módulo selectors ofrece una capa portable para monitorizar varios objetos de I/O y descubrir cuáles están listos para lectura o escritura. Selecciona automáticamente una implementación eficiente del sistema, como epoll, kqueue, poll o select. Es útil en servidores, proxies, clientes concurrentes y protocolos con muchas conexiones no bloqueantes.

Un selector no realiza el I/O por ti. Solo informa readiness. El programa todavía debe aceptar conexiones, manejar lecturas y escrituras parciales, mantener buffers, detectar EOF y aplicar timeouts. Para sistemas mayores, asyncio ofrece una abstracción más completa, pero selectors muestra la capa fundamental.

Crea el selector predeterminado

DefaultSelector elige la implementación recomendada para la plataforma.

import selectors

selector = selectors.DefaultSelector()

Ciérralo durante shutdown para liberar descriptores internos.

Registra un socket

El socket debe ser no bloqueante.

import socket

servidor = socket.socket()
servidor.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
servidor.bind(("127.0.0.1", 9000))
servidor.listen()
servidor.setblocking(False)

selector.register(servidor, selectors.EVENT_READ, data=None)

Read readiness en un socket de escucha significa que probablemente puede aceptarse una conexión.

select

selector.select(timeout) devuelve pares de SelectorKey y máscara de eventos.

eventos = selector.select(timeout=1.0)
for clave, mascara in eventos:
    if clave.data is None:
        aceptar(clave.fileobj)
    else:
        atender(clave, mascara)

Un resultado vacío tras timeout es normal y permite mantenimiento.

SelectorKey

La clave contiene el objeto registrado, descriptor, eventos solicitados y datos asociados.

Usa data para guardar dirección, buffers y fase del protocolo.

Acepta todas las conexiones pendientes

En modo no bloqueante, acepta hasta recibir BlockingIOError.

def aceptar(servidor):
    while True:
        try:
            conexion, direccion = servidor.accept()
        except BlockingIOError:
            break
        conexion.setblocking(False)
        estado = Conexion(direccion=direccion, entrada=bytearray(), salida=bytearray())
        selector.register(conexion, selectors.EVENT_READ, data=estado)

Aceptar solo una puede dejar otras esperando otro ciclo.

Lee sin bloquear

Un evento de lectura indica que la operación probablemente avanzará.

try:
    bloque = sock.recv(4096)
except BlockingIOError:
    return

if bloque:
    estado.entrada.extend(bloque)
else:
    cerrar_conexion(sock)

Bytes vacíos indican cierre ordenado del peer.

TCP es un flujo

Una lectura no equivale a un mensaje. Los datos pueden llegar divididos o agrupados.

Implementa framing con delimitador, prefijo de longitud o formato documentado. Consulta socket en Python.

Buffers de entrada

Acumula bytes hasta tener un frame completo.

estado.entrada.extend(bloque)
while mensaje_completo(estado.entrada):
    mensaje = extraer_mensaje(estado.entrada)
    procesar(mensaje, estado)

Define un límite para impedir datos infinitos sin completar un frame.

Escritura parcial

send() puede escribir solo parte del buffer.

if estado.salida:
    try:
        enviados = sock.send(estado.salida)
    except BlockingIOError:
        enviados = 0
    del estado.salida[:enviados]

Conserva los bytes restantes para el próximo evento.

Registra EVENT_WRITE solo cuando haga falta

Los sockets suelen aparecer como escribibles casi siempre. Mantener write interest activo puede crear busy loop.

Añádelo cuando el buffer de salida tenga datos y elimínalo al vaciarse.

modify

modify() cambia eventos y datos asociados.

eventos = selectors.EVENT_READ
if estado.salida:
    eventos |= selectors.EVENT_WRITE
selector.modify(sock, eventos, data=estado)

Centraliza el cálculo de la máscara.

unregister y close

Retira el objeto del selector antes de cerrarlo.

def cerrar_conexion(sock):
    try:
        selector.unregister(sock)
    except Exception:
        pass
    sock.close()

En producción, captura errores esperados específicamente.

Errores de conexión

Reset, broken pipe y fallos de red pueden surgir durante lectura o escritura.

Cierra la conexión afectada y conserva el loop salvo que falle el socket de escucha o el selector.

Timeouts por conexión

El timeout de select() no crea automáticamente un timeout de protocolo.

Guarda la última actividad y cierra clientes inactivos.

ahora = time.monotonic()
for clave in list(selector.get_map().values()):
    estado = clave.data
    if estado and ahora - estado.ultima_actividad > 30:
        cerrar_conexion(clave.fileobj)

Reloj monotónico

Deadlines y duraciones deben usar time.monotonic().

Los ajustes del reloj civil no deberían cambiar timeouts.

Backpressure

Si el peer lee lentamente, el buffer de salida puede crecer.

Define límite, pausa requests nuevas, rechaza trabajo o cierra la conexión. Nunca permitas crecimiento ilimitado.

Fairness

Un cliente con mucho volumen no debe monopolizar el loop.

Limita bytes, mensajes o tiempo por conexión en cada ciclo.

Trabajo bloqueante

Base de datos, filesystem lento y CPU pesada bloquean todas las conexiones si se ejecutan en el loop.

Envía el trabajo a un pool limitado o usa arquitectura async. Despierta el loop cuando llegue el resultado.

Despierta el selector

Una thread externa puede notificar al loop mediante socketpair() o un pipe apropiado.

Registra la punta de lectura y escribe un byte para interrumpir la espera.

Threads

Realiza register, modify y close preferiblemente en la thread del loop.

Otras threads pueden enviar comandos por queue y activar el wakeup.

Integración con queue

Una queue.Queue puede transportar comandos de workers al loop.

El siguiente artículo detallará colas sincronizadas y shutdown.

TLS

Los sockets TLS no bloqueantes pueden alternar entre lectura y escritura durante handshake y operaciones.

Gestiona SSLWantReadError y SSLWantWriteError ajustando la máscara. Un framework reduce complejidad.

Limitaciones en Windows

La posibilidad de seleccionar objetos distintos de sockets varía en Windows. Pipes y archivos comunes no funcionan igual que en Unix.

Prueba la plataforma objetivo.

Archivos regulares

Los archivos comunes suelen considerarse siempre listos y ganan poco con selectors.

Usa lectura por chunks o threads para I/O de archivos.

Señales

La espera puede ser interrumpida por signals según sistema y versión.

Mantén handlers mínimos y usa una flag o wakeup para shutdown.

Cierre graceful

Deja de aceptar conexiones, permite drenar buffers, aplica deadline y cierra las restantes.

Cierra registros, socket de escucha, wakeup y selector.

Observabilidad

Registra conexiones activas, bytes, mensajes, tamaños de buffers, duración, timeouts y errores.

Métricas de salida y latencia del loop revelan backpressure y callbacks bloqueantes.

Seguridad

Limita conexiones por origen, tamaño de frame, buffer, inactividad y tasa de mensajes.

Valida campos antes de reservar memoria y no uses IP como única identidad.

Pruebas

Prueba lectura parcial, escritura parcial, varios frames juntos, EOF, reset, cliente lento, buffers llenos, timeout, wakeup, shutdown, IPv6 y Windows.

Usa sockets locales y deadlines amplios.

Errores comunes

Los fallos frecuentes son mantener EVENT_WRITE siempre activo, asumir mensajes completos, ignorar send parcial, ejecutar trabajo bloqueante, olvidar unregister, no limitar buffers y modificar el selector desde varias threads sin protocolo.

Conclusión

selectors ofrece multiplexación portable para muchos sockets no bloqueantes. Registra solo intereses necesarios, mantiene buffers por conexión, aplica timeouts y backpressure y limpia recursos explícitamente.

Consulta la documentación oficial de selectors, socket en Python y select en Python.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Visualización de datos y distribución estadística para statistics.kde_random en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    statistics.kde_random: genera muestras de una KDE

    Aprende statistics.kde_random en Python para generar muestras desde una densidad estimada por kernel con ancho de banda, semillas, validación y

    Ler mais

    Tempo de leitura: 6 minutos
    13/10/2026
    Entorno Linux que representa namespaces y automatización con os.setns en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    os.setns: entra en namespaces Linux con seguridad

    Aprende os.setns en Python para entrar en namespaces Linux con permisos, validación, pruebas y limpieza segura.

    Ler mais

    Tempo de leitura: 5 minutos
    13/10/2026
    Computadora con código que representa copia de archivos con Path.copy en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Path.copy: copia archivos y carpetas con seguridad

    Aprende Path.copy en Python para copiar archivos y directorios con seguridad, control, rendimiento y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    12/10/2026
    Código en un monitor que representa acceso a memoria nativa con ctypes.memoryview_at en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ctypes.memoryview_at: memoria nativa sin copias

    Aprende ctypes.memoryview_at en Python para acceder a memoria nativa sin copias, validar punteros y reducir riesgos.

    Ler mais

    Tempo de leitura: 7 minutos
    12/10/2026
    Código Python en pantalla representando EnumDict y enumeraciones avanzadas
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    EnumDict: personaliza Enums con validación

    Aprende EnumDict en Python para personalizar EnumType, validar miembros y crear enumeraciones avanzadas con seguridad.

    Ler mais

    Tempo de leitura: 4 minutos
    11/10/2026
    Rack de servidores que representa el balanceo de conexiones con SO_REUSEPORT_LB en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    SO_REUSEPORT_LB: reparte conexiones entre workers

    Aprende SO_REUSEPORT_LB en Python para distribuir conexiones entre workers con pruebas, portabilidad y cierre ordenado.

    Ler mais

    Tempo de leitura: 5 minutos
    11/10/2026