HOWTO · Python

OSError: [WinError 10038] se intentó una operación en algo que no es un socket

Con esta explicación, aprenderemos qué son servidor y cliente y cómo usar el módulo de socket para crear una aplicación con la ayuda de servidor y cliente. También aprenderemos a evitar errores al realizar este tipo de aplicaciones en Python.

En esta página

OSError: [WinError 10038] indica que se intentó realizar una operación en algo que no es un socket activo. En el patrón de servidor defectuoso que se muestra más abajo, se cierra el socket del cliente aceptado y luego se vuelve a usar dentro de un bucle interno. Atiende cada conexión una sola vez y después vuelve a accept() para aceptar al siguiente cliente.

Crear un ejemplo mínimo de cliente y servidor con sockets en Python

Un servidor escucha conexiones en un socket de escucha. Cuando accept() devuelve un resultado, proporciona al servidor un nuevo socket conectado para ese cliente; el socket de escucha sigue disponible para aceptar conexiones posteriores. El cliente crea su propio socket y llama a connect() con la misma dirección y el mismo puerto.

Guarda este código como socket_demo.py. Si se ejecuta sin argumentos, inicia un hilo de servidor y un cliente para hacer una demostración local sencilla. Los modos server y client también permiten ejecutar cada extremo en un proceso independiente.

import socket
import sys
import threading

HOST = "127.0.0.1"
PORT = 6060
MESSAGE = b"Welcome to the server!"


def serve(ready=None):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as listener:
        listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        listener.bind((HOST, PORT))
        listener.listen(1)
        if ready is not None:
            ready.set()
        connection, address = listener.accept()
        with connection:
            print(f"Connected: {address[0]}")
            connection.sendall(MESSAGE)


def receive():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
        client.connect((HOST, PORT))
        chunks = []
        while True:
            chunk = client.recv(2048)
            if not chunk:
                break
            chunks.append(chunk)
        message = b"".join(chunks)
        print(message.decode("utf-8"))


if len(sys.argv) > 1 and sys.argv[1] == "server":
    serve()
elif len(sys.argv) > 1 and sys.argv[1] == "client":
    receive()
else:
    ready = threading.Event()
    server = threading.Thread(target=serve, args=(ready,))
    server.start()
    ready.wait()
    receive()
    server.join()
Connected: 127.0.0.1
Welcome to the server!

El cliente recibe los datos en fragmentos hasta que el servidor cierra la conexión aceptada y, después, combina los bytes antes de decodificarlos. Así puede manejar un mensaje que llega en varias lecturas TCP. Para ejecutar los extremos en procesos separados, abre dos terminales en el directorio que contiene el archivo. En la primera, ejecuta python socket_demo.py server; el servidor esperará a un cliente. Luego, en la segunda, ejecuta python socket_demo.py client. El servidor muestra la dirección de loopback del cliente y el cliente muestra el mensaje recibido. El servidor cierra esa conexión después de enviar los datos; el socket de escucha se cierra cuando termina este ejemplo, que atiende una sola conexión.

Salida de la aplicación sin errores con el módulo socket

Diagnosticar y corregir OSError: [WinError 10038]

En el patrón defectuoso, el bucle externo llama a accept() y guarda el socket conectado en Client_Socket. El bucle interno while True envía datos una vez y cierra ese socket. En la siguiente iteración, intenta enviar datos de nuevo por el mismo socket ya cerrado, lo que puede provocar OSError: [WinError 10038]. El bucle interno no llama a accept(); como nunca termina, el bucle externo no puede aceptar otro cliente.

Para corregirlo, elimina el bucle interno incondicional y atiende una conexión aceptada en cada iteración del bucle externo: acepta la conexión, envía los datos y luego cierra el socket conectado. Mantén abierto el socket de escucha mientras el servidor deba aceptar más clientes. Cerrar el socket aceptado no cierra el socket de escucha, y ejecutar los archivos desde una terminal no causa este error.

Para diagnosticar tu propio código, identifica qué socket usa la operación que falla, comprueba si ese mismo socket ya se había cerrado y verifica que cada conexión aceptada se atienda una sola vez. En un servicio real, mantén activo el bucle de aceptación del servidor y coloca el trabajo de cada cliente en un manejador; este ejemplo atiende intencionalmente a un solo cliente y luego termina.

Ejecución del archivo actual en la ventana interactiva con el módulo socket