HOWTO · Python

OSError: [WinError 10038] ソケットではないものに対して操作が試行されました

この説明では、サーバーとクライアントとは何か、ソケットモジュールを使用してサーバーとクライアントの助けを借りてアプリケーションを作成する方法を学びます。 また、Python でこれらのタイプのアプリケーションを作成する際のエラーを回避する方法も学びます。

このページの内容

OSError: [WinError 10038] は、有効なソケットではないものに対して操作を試みたことを示します。以下の誤ったサーバー処理では、接続を受け付けたクライアントソケットを閉じた後、内側のループで再利用しています。各接続を1回処理したら、次のクライアントを受け付けるために accept() に戻ります。

Pythonで最小限のクライアント・サーバーソケット例を作成する

サーバーは待ち受けソケットで接続を待ち受けます。accept() が戻ると、そのクライアントと接続された新しいソケットがサーバーに渡されます。待ち受けソケットは、後続の接続にも使える状態のままです。クライアントは独自のソケットを作成し、同じアドレスとポートを指定して connect() を呼び出します。

このコードを socket_demo.py として保存します。引数なしで実行すると、簡潔なローカルデモ用にサーバースレッドとクライアントが起動します。server モードと client モードを使えば、両端を別々のプロセスとして実行できます。

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!

クライアントは、サーバーが受け付けた接続を閉じるまでデータを分割して読み取り、その後、デコードする前にバイト列を結合します。これにより、メッセージが複数回のTCP読み取りに分かれて届く場合にも対応できます。別々のプロセスで実行するには、ファイルがあるディレクトリでターミナルを2つ開きます。1つ目のターミナルで python socket_demo.py server を実行すると、サーバーはクライアントを1つ待ち受けます。次に2つ目のターミナルで python socket_demo.py client を実行します。サーバーにはクライアントのループバックアドレスが表示され、クライアントには受信したメッセージが表示されます。サーバーは送信後にその接続を閉じ、この1接続だけを扱う例が終了すると待ち受けソケットも閉じます。

socketモジュールを使ったエラーのないアプリケーションの出力

OSError: [WinError 10038] の原因を特定して修正する

誤った処理では、外側のループが accept() を呼び出し、接続済みソケットを Client_Socket に格納します。内側の while True は1回送信した後、そのソケットを閉じます。次の反復では、同じ閉じたソケットを使って送信しようとするため、OSError: [WinError 10038] が発生することがあります。内側のループは accept() を呼び出しません。また、このループが終了しないため、外側のループは次のクライアントを受け付けられません。

修正するには、無条件に繰り返す内側のループを削除し、外側のループの各反復で受け付けた接続を1つずつ処理します。つまり、接続を受け付け、送信し、接続済みソケットを閉じます。サーバーがさらにクライアントを受け付ける間は、待ち受けソケットを開いたままにします。受け付けた接続を閉じても、待ち受けソケットは閉じません。また、ファイルをターミナルから実行することがエラーの原因ではありません。

自分のコードを調べるときは、失敗した操作で使われているソケットを特定し、そのソケットがすでに閉じられていないか、受け付けた各接続を1回ずつ処理しているかを確認してください。実際のサービスではサーバーの受付ループを動かし続け、クライアントごとの処理をハンドラーに分けます。この例は意図的にクライアントを1つだけ処理して終了します。

ソケットモジュールを使ったアプリケーション:インタラクティブウィンドウで現在のファイルを実行