socketserver —ネットワークサーバーのフレームワーク—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.8/library/socketserver
移動先:案内検索

socketserver —ネットワークサーバーのフレームワーク

ソースコード: :source: `Lib / socketserver.py`



socketserver モジュールは、ネットワークサーバーを作成するタスクを簡素化します。

4つの基本的な具象サーバークラスがあります。

class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)
これは、クライアントとサーバー間のデータの継続的なストリームを提供するインターネットTCPプロトコルを使用します。 bind_and_activate がtrueの場合、コンストラクターは自動的に server_bind()および server_activate()を呼び出そうとします。 その他のパラメータは、 BaseServer 基本クラスに渡されます。
class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)
これは、データグラムを使用します。データグラムは、情報の個別のパケットであり、順序が狂って到着したり、転送中に失われたりする可能性があります。 パラメータは TCPServer と同じです。
class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)

class socketserver.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True)

これらの使用頻度の低いクラスは、TCPおよびUDPクラスに似ていますが、Unixドメインソケットを使用します。 Unix以外のプラットフォームでは使用できません。 パラメータは TCPServer と同じです。

これらの4つのクラスは、リクエストを同期的に処理します。 次のリクエストを開始する前に、各リクエストを完了する必要があります。 これは、各リクエストの完了に長い時間がかかる場合、大量の計算が必要な場合、またはクライアントが処理するのに時間がかかる大量のデータを返すため、適切ではありません。 解決策は、各リクエストを処理するための個別のプロセスまたはスレッドを作成することです。 ForkingMixIn および ThreadingMixIn ミックスインクラスを使用して、非同期動作をサポートできます。

サーバーの作成にはいくつかの手順が必要です。 まず、 BaseRequestHandler クラスをサブクラス化し、その handle()メソッドをオーバーライドして、リクエストハンドラークラスを作成する必要があります。 このメソッドは、着信要求を処理します。 次に、サーバークラスの1つをインスタンス化し、サーバーのアドレスとリクエストハンドラークラスを渡す必要があります。 with ステートメントでサーバーを使用することをお勧めします。 次に、サーバーオブジェクトの handle_request()または serve_forever()メソッドを呼び出して、1つまたは複数の要求を処理します。 最後に、 server_close()を呼び出してソケットを閉じます(withステートメントを使用した場合を除く)。

スレッド接続の動作のために ThreadingMixIn から継承する場合は、突然のシャットダウン時にスレッドがどのように動作するかを明示的に宣言する必要があります。 ThreadingMixIn クラスは、サーバーがスレッドの終了を待機する必要があるかどうかを示す属性ダイヤモンドスレッドを定義します。 スレッドを自律的に動作させる場合は、フラグを明示的に設定する必要があります。 デフォルトは False です。これは、 ThreadingMixIn によって作成されたすべてのスレッドが終了するまでPythonが終了しないことを意味します。

サーバークラスは、使用するネットワークプロトコルに関係なく、同じ外部メソッドと属性を持っています。

サーバー作成に関する注意事項

継承図には5つのクラスがあり、そのうちの4つは4つのタイプの同期サーバーを表します。

+------------+
| BaseServer |
+------------+
      |
      v
+-----------+        +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+        +------------------+
      |
      v
+-----------+        +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+        +--------------------+

UnixDatagramServerUnixStreamServer からではなく UDPServer から派生していることに注意してください。IPとUnixストリームサーバーの唯一の違いはアドレスファミリであり、これは両方のUnixサーバークラス。

class socketserver.ForkingMixIn
class socketserver.ThreadingMixIn

これらのミックスインクラスを使用して、各タイプのサーバーのフォークバージョンとスレッドバージョンを作成できます。 たとえば、 ThreadingUDPServer は次のように作成されます。

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

UDPServer で定義されたメソッドをオーバーライドするため、ミックスインクラスが最初に来ます。 さまざまな属性を設定すると、基盤となるサーバーメカニズムの動作も変更されます。

ForkingMixIn および以下で説明するForkingクラスは、 fork()をサポートするPOSIXプラットフォームでのみ使用できます。

socketserver.ForkingMixIn.server_close()は、socketserver.ForkingMixIn.block_on_close属性がfalseの場合を除いて、すべての子プロセスが完了するまで待機します。

socketserver.ThreadingMixIn.server_close()は、socketserver.ThreadingMixIn.block_on_close属性がfalseの場合を除いて、すべての非デーモンスレッドが完了するまで待機します。 ThreadingMixIn.daemon_threadsTrueに設定してデーモンスレッドを使用し、スレッドが完了するまで待機しないようにします。

バージョン3.7で変更: socketserver.ForkingMixIn.server_close()およびsocketserver.ThreadingMixIn.server_close()は、すべての子プロセスと非デーモンスレッドが完了するまで待機するようになりました。 新しいsocketserver.ForkingMixIn.block_on_closeクラス属性を追加して、3.7より前の動作をオプトインします。

class socketserver.ForkingTCPServer

class socketserver.ForkingUDPServer
class socketserver.ThreadingTCPServer
class socketserver.ThreadingUDPServer

これらのクラスは、ミックスインクラスを使用して事前定義されています。

サービスを実装するには、 BaseRequestHandler からクラスを派生させ、その handle()メソッドを再定義する必要があります。 次に、サーバークラスの1つをリクエストハンドラークラスと組み合わせて、さまざまなバージョンのサービスを実行できます。 リクエストハンドラクラスは、データグラムまたはストリームサービスで異なっている必要があります。 これは、ハンドラーサブクラス StreamRequestHandler または DatagramRequestHandler を使用して非表示にできます。

もちろん、あなたはまだあなたの頭を使わなければなりません! たとえば、子プロセスの変更が親プロセスに保持されて各子に渡される初期状態に達することはないため、サービスにさまざまな要求によって変更できる状態がメモリ内に含まれている場合、フォークサーバーを使用することは意味がありません。 。 この場合、スレッドサーバーを使用できますが、共有データの整合性を保護するために、おそらくロックを使用する必要があります。

一方、すべてのデータが外部(たとえば、ファイルシステム)に保存されるHTTPサーバーを構築している場合、同期クラスは基本的に、1つの要求が処理されている間、サービスを「聴覚障害者」にします。クライアントが要求したすべてのデータの受信に時間がかかる場合、非常に長い時間がかかります。 ここでは、スレッドサーバーまたはフォークサーバーが適切です。

場合によっては、リクエストの一部を同期的に処理することが適切な場合もありますが、リクエストデータによっては、フォークされた子で処理を終了することもできます。 これは、同期サーバーを使用し、リクエストハンドラークラス handle()メソッドで明示的なフォークを実行することで実装できます。

スレッドも fork()もサポートしない環境(またはこれらが高すぎるかサービスに不適切な環境)で複数の同時リクエストを処理する別のアプローチは、部分的に終了したリクエストの明示的なテーブルを維持して使用することです。 セレクターは、次に処理する要求(または新しい着信要求を処理するかどうか)を決定します。 これは、各クライアントが長時間接続される可能性があるストリームサービスにとって特に重要です(スレッドまたはサブプロセスを使用できない場合)。 これを管理する別の方法については、 asyncore を参照してください。


サーバーオブジェクト

class socketserver.BaseServer(server_address, RequestHandlerClass)

これは、モジュール内のすべてのサーバーオブジェクトのスーパークラスです。 以下に示すインターフェースを定義しますが、サブクラスで実行されるほとんどのメソッドを実装していません。 2つのパラメーターは、それぞれの server_address 属性と RequestHandlerClass 属性に格納されます。

fileno()

サーバーがリッスンしているソケットの整数ファイル記述子を返します。 この関数は、最も一般的にセレクターに渡され、同じプロセスで複数のサーバーを監視できるようにします。

handle_request()

単一のリクエストを処理します。 この関数は、 get_request()verify_request()、および process_request()のメソッドを順番に呼び出します。 ハンドラークラスのユーザー提供の handle()メソッドで例外が発生した場合、サーバーの handle_error()メソッドが呼び出されます。 timeout 秒以内にリクエストが受信されない場合、 handle_timeout()が呼び出され、 handle_request()が返されます。

serve_forever(poll_interval=0.5)

明示的な shutdown()リクエストまでリクエストを処理します。 poll_interval 秒ごとにシャットダウンをポーリングします。 timeout 属性を無視します。 また、 service_actions()を呼び出します。これは、特定のサービスに固有のアクションを提供するためにサブクラスまたはミックスインによって使用される場合があります。 たとえば、 ForkingMixIn クラスは、 service_actions()を使用してゾンビの子プロセスをクリーンアップします。

バージョン3.3で変更: serve_foreverメソッドにservice_actions呼び出しを追加しました。

service_actions()

これは serve_forever()ループで呼び出されます。 このメソッドは、サブクラスまたはミックスインクラスによってオーバーライドされ、クリーンアップアクションなど、特定のサービスに固有のアクションを実行できます。

バージョン3.3の新機能。

shutdown()

serve_forever()ループに停止して、停止するまで待つように指示します。 shutdown()は、 serve_forever()が別のスレッドで実行されているときに呼び出す必要があります。そうしないと、デッドロックが発生します。

server_close()

サーバーをクリーンアップします。 上書きされる可能性があります。

address_family

サーバーのソケットが属するプロトコルのファミリー。 一般的な例は、 socket.AF_INET および socket.AF_UNIX です。

RequestHandlerClass

ユーザー提供のリクエストハンドラークラス。 このクラスのインスタンスは、リクエストごとに作成されます。

server_address

サーバーがリッスンしているアドレス。 アドレスの形式は、プロトコルファミリによって異なります。 詳細については、 socket モジュールのドキュメントを参照してください。 インターネットプロトコルの場合、これはアドレスを示す文字列と整数のポート番号を含むタプルです。たとえば、('127.0.0.1', 80)です。

socket

サーバーが着信要求をリッスンするソケットオブジェクト。

サーバークラスは、次のクラス変数をサポートします。

allow_reuse_address

サーバーがアドレスの再利用を許可するかどうか。 これはデフォルトで False であり、サブクラスで設定してポリシーを変更できます。

request_queue_size

リクエストキューのサイズ。 1つのリクエストの処理に時間がかかる場合、サーバーがビジー状態のときに到着したリクエストは、最大 request_queue_size リクエストまでキューに入れられます。 キューがいっぱいになると、クライアントからのそれ以上の要求は「接続が拒否されました」エラーを受け取ります。 デフォルト値は通常5ですが、これはサブクラスによってオーバーライドできます。

socket_type

サーバーが使用するソケットのタイプ。 socket.SOCK_STREAMsocket.SOCK_DGRAM は2つの一般的な値です。

timeout

秒単位で測定されるタイムアウト期間、またはタイムアウトが必要ない場合は Nonehandle_request()がタイムアウト期間内に着信要求を受信しない場合、 handle_timeout()メソッドが呼び出されます。

TCPServer のような基本サーバークラスのサブクラスによってオーバーライドできるさまざまなサーバーメソッドがあります。 これらのメソッドは、サーバーオブジェクトの外部ユーザーには役立ちません。

finish_request(request, client_address)

RequestHandlerClass をインスタンス化し、その handle()メソッドを呼び出すことにより、実際にリクエストを処理します。

get_request()

ソケットからの要求を受け入れ、クライアントとの通信に使用される new ソケットオブジェクトとクライアントのアドレスを含む2タプルを返す必要があります。

handle_error(request, client_address)

この関数は、 RequestHandlerClass インスタンスの handle()メソッドで例外が発生した場合に呼び出されます。 デフォルトのアクションは、標準エラーへのトレースバックを出力し、それ以降の要求の処理を続行することです。

バージョン3.6で変更: Exception クラスから派生した例外のみが呼び出されるようになりました。

handle_timeout()

この関数は、 timeout 属性が None 以外の値に設定され、要求が受信されずにタイムアウト期間が経過したときに呼び出されます。 サーバーをフォークするためのデフォルトのアクションは、終了した子プロセスのステータスを収集することですが、スレッドサーバーではこのメソッドは何もしません。

process_request(request, client_address)

finish_request()を呼び出して、 RequestHandlerClass のインスタンスを作成します。 必要に応じて、この関数は要求を処理するための新しいプロセスまたはスレッドを作成できます。 ForkingMixIn および ThreadingMixIn クラスがこれを行います。

server_activate()

サーバーをアクティブ化するためにサーバーのコンストラクターによって呼び出されます。 TCPサーバーのデフォルトの動作は、サーバーのソケットで listen()を呼び出すだけです。 上書きされる可能性があります。

server_bind()

サーバーのコンストラクターによって呼び出され、ソケットを目的のアドレスにバインドします。 上書きされる可能性があります。

verify_request(request, client_address)

ブール値を返す必要があります。 値が True の場合、要求は処理され、 False の場合、要求は拒否されます。 この関数をオーバーライドして、サーバーのアクセス制御を実装できます。 デフォルトの実装は常に True を返します。

バージョン3.6で変更: context manager プロトコルのサポートが追加されました。 コンテキストマネージャーを終了することは、 server_close()を呼び出すことと同じです。


リクエストハンドラオブジェクト

class socketserver.BaseRequestHandler

これは、すべてのリクエストハンドラオブジェクトのスーパークラスです。 以下に示すインターフェースを定義します。 具体的なリクエストハンドラサブクラスは、新しい handle()メソッドを定義する必要があり、他のメソッドをオーバーライドできます。 サブクラスの新しいインスタンスは、リクエストごとに作成されます。

setup()

handle()メソッドの前に呼び出され、必要な初期化アクションを実行します。 デフォルトの実装は何もしません。

handle()

この関数は、要求を処理するために必要なすべての作業を実行する必要があります。 デフォルトの実装は何もしません。 いくつかのインスタンス属性を使用できます。 リクエストはself.requestとして利用できます。 self.client_addressとしてのクライアントアドレス。 サーバーごとの情報にアクセスする必要がある場合は、サーバーインスタンスをself.serverとして指定します。

self.requestのタイプは、データグラムサービスとストリームサービスで異なります。 ストリームサービスの場合、self.requestはソケットオブジェクトです。 データグラムサービスの場合、self.requestは文字列とソケットのペアです。

finish()

handle()メソッドの後に呼び出され、必要なクリーンアップアクションを実行します。 デフォルトの実装は何もしません。 setup()で例外が発生した場合、この関数は呼び出されません。

class socketserver.StreamRequestHandler
class socketserver.DatagramRequestHandler

これらの BaseRequestHandler サブクラスは、 setup()および finish()メソッドをオーバーライドし、self.rfileおよびself.wfile属性を提供します。 self.rfile属性とself.wfile属性は、それぞれ読み取りまたは書き込みを行って、要求データを取得したり、クライアントにデータを返したりすることができます。

両方のクラスのrfile属性は、 io.BufferedIOBase 読み取り可能インターフェイスをサポートし、DatagramRequestHandler.wfileio.BufferedIOBase 書き込み可能インターフェイスをサポートします。

バージョン3.6で変更: StreamRequestHandler.wfileは、 io.BufferedIOBase 書き込み可能インターフェイスもサポートします。


socketserver.TCPServer の例

これはサーバー側です:

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    The request handler class for our server.

    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """

    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    # Create the server, binding to localhost on port 9999
    with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
        # Activate the server; this will keep running until you
        # interrupt the program with Ctrl-C
        server.serve_forever()

ストリーム(標準のファイルインターフェイスを提供することで通信を簡素化するファイルのようなオブジェクト)を利用する代替の要求ハンドラークラス:

class MyTCPHandler(socketserver.StreamRequestHandler):

    def handle(self):
        # self.rfile is a file-like object created by the handler;
        # we can now use e.g. readline() instead of raw recv() calls
        self.data = self.rfile.readline().strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # Likewise, self.wfile is a file-like object used to write back
        # to the client
        self.wfile.write(self.data.upper())

違いは、2番目のハンドラーでのreadline()呼び出しは、改行文字に遭遇するまでrecv()を複数回呼び出すのに対し、最初のハンドラーでの1回のrecv()呼び出しはただ戻ることです。 1回のsendall()呼び出しでクライアントから送信されたもの。

これはクライアント側です:

import socket
import sys

HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])

# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    # Connect to server and send data
    sock.connect((HOST, PORT))
    sock.sendall(bytes(data + "\n", "utf-8"))

    # Receive data from the server and shut down
    received = str(sock.recv(1024), "utf-8")

print("Sent:     {}".format(data))
print("Received: {}".format(received))

例の出力は次のようになります。

サーバ:

$ python TCPServer.py
127.0.0.1 wrote:
b'hello world with TCP'
127.0.0.1 wrote:
b'python is nice'

クライアント:

$ python TCPClient.py hello world with TCP
Sent:     hello world with TCP
Received: HELLO WORLD WITH TCP
$ python TCPClient.py python is nice
Sent:     python is nice
Received: PYTHON IS NICE

socketserver.UDPServer の例

これはサーバー側です:

import socketserver

class MyUDPHandler(socketserver.BaseRequestHandler):
    """
    This class works similar to the TCP handler class, except that
    self.request consists of a pair of data and client socket, and since
    there is no connection the client address must be given explicitly
    when sending data back via sendto().
    """

    def handle(self):
        data = self.request[0].strip()
        socket = self.request[1]
        print("{} wrote:".format(self.client_address[0]))
        print(data)
        socket.sendto(data.upper(), self.client_address)

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999
    with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
        server.serve_forever()

これはクライアント側です:

import socket
import sys

HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])

# SOCK_DGRAM is the socket type to use for UDP sockets
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# As you can see, there is no connect() call; UDP has no connections.
# Instead, data is directly sent to the recipient via sendto().
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")

print("Sent:     {}".format(data))
print("Received: {}".format(received))

この例の出力は、TCPサーバーの例とまったく同じようになります。


非同期ミックスイン

非同期ハンドラーを構築するには、 ThreadingMixIn クラスと ForkingMixIn クラスを使用します。

ThreadingMixIn クラスの例:

import socket
import threading
import socketserver

class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):

    def handle(self):
        data = str(self.request.recv(1024), 'ascii')
        cur_thread = threading.current_thread()
        response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
        self.request.sendall(response)

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass

def client(ip, port, message):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((ip, port))
        sock.sendall(bytes(message, 'ascii'))
        response = str(sock.recv(1024), 'ascii')
        print("Received: {}".format(response))

if __name__ == "__main__":
    # Port 0 means to select an arbitrary unused port
    HOST, PORT = "localhost", 0

    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    with server:
        ip, port = server.server_address

        # Start a thread with the server -- that thread will then start one
        # more thread for each request
        server_thread = threading.Thread(target=server.serve_forever)
        # Exit the server thread when the main thread terminates
        server_thread.daemon = True
        server_thread.start()
        print("Server loop running in thread:", server_thread.name)

        client(ip, port, "Hello World 1")
        client(ip, port, "Hello World 2")
        client(ip, port, "Hello World 3")

        server.shutdown()

例の出力は次のようになります。

$ python ThreadedTCPServer.py
Server loop running in thread: Thread-1
Received: Thread-2: Hello World 1
Received: Thread-3: Hello World 2
Received: Thread-4: Hello World 3

ForkingMixIn クラスは同じように使用されますが、サーバーがリクエストごとに新しいプロセスを生成する点が異なります。 fork()をサポートするPOSIXプラットフォームでのみ使用できます。