socketserver --- ネットワークサーバーのフレームワーク¶
ソースコード: Lib/socketserver.py
socketserver モジュールはネットワークサーバを実装するタスクを単純化します。
利用可能な環境: not WASI.
このモジュールは WebAssembly では動作しないか、利用不可です。詳しくは、WebAssembly プラットフォーム を見てください。
基本的な具象サーバクラスが4つあります:
- class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
クライアントとサーバの間で連続したデータストリームを提供する、インターネットの TCP プロトコルを使います。 bind_and_activate が真の場合、コンストラクターは
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 つのクラスは要求を 同期的に (synchronously) 処理します; 各要求は次の要求を開始する前に完結していなければなりません。同期的な処理は、サーバで大量の計算を必要とする、あるいはクライアントが処理するには時間がかかりすぎるような大量のデータを返す、といった理由によってリクエストに長い時間がかかる状況には向いていません。こうした状況の解決方法は別のプロセスを生成するか、個々の要求を扱うスレッドを生成することです; ForkingMixIn および ThreadingMixIn 配合クラス (mix-in classes) を使えば、非同期的な動作をサポートできます。
サーバの作成にはいくつかのステップが必要です。最初に、 BaseRequestHandler クラスをサブクラス化して要求処理クラス (request hander class) を生成し、その handle() メソッドをオーバーライドしなければなりません; このメソッドは入力される要求を処理します。次に、サーバクラスのうち一つを、サーバのアドレスと要求処理クラスを渡してインスタンス化しなければなりません。サーバを with 文中で使うことが推奨されます。そして、handle_request() または serve_forever() メソッドを呼び出して、一つのまたは多数の要求を処理します。最後に、(with 文を使っていなかったなら) server_close() を呼び出してソケットを閉じます。
ThreadingMixIn から継承してスレッドを利用した接続を行う場合、突然終了する場合のスレッドの動作を明示的に指定する必要があります。
ThreadingMixIn クラスには daemon_threads 属性があり、サーバがスレッドの終了を待ち合わせるかどうかを指定する事ができます。
スレッドの終了を待たずに終了させたい場合は、このフラグを明示的に設定してください。
デフォルトは False で、Pythonは ThreadingMixIn クラスが起動した全てのスレッドが終了するまで実行し続けます。
サーバクラス群は使用するネットワークプロトコルに関わらず、同じ外部メソッドおよび属性を持ちます。
サーバ生成に関するノート¶
継承図にある五つのクラスのうち四つは四種類の同期サーバを表わしています:
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
UnixDatagramServer は UDPServer から派生していて、 UnixStreamServer からではないことに注意してください --- IP と Unix サーバの唯一の違いはアドレスファミリーです。
- class socketserver.ForkingMixIn¶
- class socketserver.ThreadingMixIn¶
それぞれのタイプのサーバのフォークしたりスレッド実行したりするバージョンはそれらのミクシン(mix-in)クラスを使って作ることができます。たとえば、
ThreadingUDPServerは以下のようにして作られます:class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
ミクシンクラスは
UDPServerで定義されるメソッドをオーバライドするために、最初に来ます。様々な属性を設定することで元になるサーバ機構の振る舞いを変えられます。ForkingMixInと以下で説明する Forking クラスは、fork()をサポートする POSIX プラットフォームでのみ利用できます。- block_on_close¶
ForkingMixIn.server_closeは、block_on_close属性がFalseでない限り、すべての子プロセスの終了を待ちます。ThreadingMixIn.server_closeは、block_on_close属性がFalseでない限り、すべての非デーモンスレッドの終了を待ちます。
- daemon_threads¶
ThreadingMixInでスレッドの終了を待たないようにするには、ThreadingMixIn.daemon_threadsをTrueに設定してデーモンスレッドを使ってください。
バージョン 3.7 で変更:
ForkingMixIn.server_closeとThreadingMixIn.server_closeは、すべての子プロセスと非デーモンスレッドが終了するまで待つようになりました。 3.7 より前の動作を選べるよう、クラス属性ForkingMixIn.block_on_closeが追加されました。
- class socketserver.ForkingTCPServer¶
- class socketserver.ForkingUDPServer¶
- class socketserver.ThreadingTCPServer¶
- class socketserver.ThreadingUDPServer¶
- class socketserver.ForkingUnixStreamServer¶
- class socketserver.ForkingUnixDatagramServer¶
- class socketserver.ThreadingUnixStreamServer¶
- class socketserver.ThreadingUnixDatagramServer¶
これらのクラスは、ミクシンクラスを使ってあらかじめ定義されています。
Added in version 3.12: ForkingUnixStreamServer クラスと ForkingUnixDatagramServer クラスが追加されました。
サービスの実装には、 BaseRequestHandler からクラスを派生させてその handle() メソッドを再定義しなければなりません。このようにすれば、サーバクラスと要求処理クラスを結合して様々なバージョンのサービスを実行することができます。要求処理クラスはデータグラムサービスかストリームサービスかで異なることでしょう。この違いは処理サブクラス StreamRequestHandler または DatagramRequestHandler を使うという形で隠蔽できます。
もちろん、まだ頭を使わなければなりません! たとえば、サービスがリクエストによっては書き換えられるようなメモリ上の状態を使うならば、フォークするサーバを使うのは馬鹿げています。というのも子プロセスでの書き換えは親プロセスで保存されている初期状態にも親プロセスから分配される各子プロセスの状態にも届かないからです。この場合、スレッド実行するサーバを使うことはできますが、共有データの一貫性を保つためにロックを使わなければならなくなるでしょう。
一方、全てのデータが外部に(たとえばファイルシステムに)保存される HTTP サーバを作っているのだとすると、同期クラスではどうしても一つの要求が処理されている間サービスが「耳の聞こえない」状態を呈することになります --- この状態はもしクライアントが要求した全てのデータをゆっくり受け取るととても長い時間続きかねません。こういう場合にはサーバをスレッド実行したりフォークすることが適切です。
ある場合には、要求の一部を同期的に処理する一方で、要求データに依って子プロセスをフォークして処理を終了させる、といった方法も適当かもしれません。こうした処理方法は同期サーバを使って要求処理クラスの handle() メソッドの中で自分でフォークするようにして実装することができます。
スレッドも fork() もサポートされていない環境 (あるいはコストが高すぎたり、そのサービスに適さなかったりする場合) で複数の要求を同時に処理する方法もあります。
処理途中の要求を明示的に表で管理し、selectors を使って次に処理する要求 (または新しく届いた要求を処理するかどうか) を決める方法です。
これは、各クライアントが長時間接続し続ける可能性のあるストリームサービスで、スレッドやサブプロセスを使えない場合に特に重要です。
Serverオブジェクト¶
- class socketserver.BaseServer(server_address, RequestHandlerClass)¶
このモジュールのすべてのサーバオブジェクトのスーパークラスです。 以下のインターフェースを定義しますが、ほとんどのメソッドは実装せず、サブクラスで実装します。 2 つの引数は、それぞれ
server_address属性とRequestHandlerClass属性に格納されます。- fileno()¶
サーバが要求待ちを行っているソケットのファイル記述子を整数で返します。この関数は一般的に、同じプロセス中の複数のサーバを監視できるようにするために、
selectorsに渡されます。
- 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()のループ内で呼び出されます。 サブクラスやミクシンクラスでオーバーライドして、クリーンアップなどのサービス固有の処理を行えます。Added in version 3.3.
- shutdown()¶
serve_forever()のループに停止を指示し、停止するまで待ちます。shutdown()は、serve_forever()が別のスレッドで実行されている間に呼び出す必要があります。 そうしないとデッドロックします。
- server_close()¶
サーバをクリーンアップします。上書き出来ます。
- address_family¶
サーバのソケットが属するプロトコルファミリーです。 よく使われるものには
socket.AF_INET、socket.AF_INET6、socket.AF_UNIXがあります。 IPv6 サーバのクラスが必要な場合は、このモジュールの TCP または UDP サーバクラスを継承し、クラス属性をaddress_family = AF_INET6と設定してください。
- RequestHandlerClass¶
ユーザが提供する要求処理クラスです; 要求ごとにこのクラスのインスタンスが生成されます。
- server_address¶
サーバが要求待ちを行うアドレスです。アドレスの形式はプロトコルファミリによって異なります。詳細は
socketモジュールを参照してください。インターネットプロトコルでは、この値は例えば('127.0.0.1', 80)のようにアドレスを与える文字列と整数のポート番号を含むタプルです。
- socket¶
サーバが入力の要求待ちを行うためのソケットオブジェクトです。
サーバクラスは以下のクラス変数をサポートします:
- request_queue_size¶
要求待ち行列 (queue) のサイズです。単一の要求を処理するのに長時間かかる場合には、サーバが処理中に届いた要求は最大
request_queue_size個まで待ち行列に置かれます。待ち行列が一杯になると、それ以降のクライアントからの要求は "接続拒否 (Connection denied)" エラーになります。標準の値は通常 5 ですが、この値はサブクラスで上書きすることができます。
- socket_type¶
サーバが使うソケットの型です; 一般的な2つの値は、
socket.SOCK_STREAMとsocket.SOCK_DGRAMです。
- timeout¶
タイムアウト時間(秒)、もしくは、タイムアウトを望まない場合に
None。handle_request()がこの時間内にリクエストを受信しない場合、handle_timeout()メソッドが呼ばれます。
TCPServerのような基底クラスのサブクラスで上書きできるサーバメソッドは多数あります; これらのメソッドはサーバオブジェクトの外部のユーザにとっては役にたたないものです。- finish_request(request, client_address)¶
RequestHandlerClassをインスタンス化し、handle()メソッドを呼び出して、実際に要求を処理します。
- get_request()¶
ソケットから要求を受理して、クライアントとの通信に使われる 新しい ソケットオブジェクト、およびクライアントのアドレスからなる、2 要素のタプルを返します。
- handle_error(request, client_address)¶
この関数は
RequestHandlerClassインスタンスのhandle()メソッドが例外を送出した際に呼び出されます。標準の動作では標準エラー出力へトレースバックを出力し、後続する要求を継続して処理します。バージョン 3.6 で変更:
Exceptionクラスから派生した例外に対してのみ呼び出すようになりました。
- handle_timeout()¶
この関数は
timeout属性がNone以外に設定されて、リクエストがないままタイムアウト秒数が過ぎたときに呼ばれます。 fork型サーバーでのデフォルトの動作は、終了した子プロセスの情報を集めるようになっています。スレッド型サーバーではこのメソッドは何もしません。
- 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()メソッドを新たに定義する必要があり、ほかのメソッドもオーバーライドできます。 要求ごとにサブクラスの新しいインスタンスを作成します。- handle()¶
この関数は、要求に応えるために必要なすべての処理を行わなければなりません。 デフォルトの実装は何もしません。 いくつかのインスタンス属性を利用できます。 要求は
request、クライアントのアドレスはclient_addressに格納されています。 サーバ固有の情報が必要な場合は、serverからサーバのインスタンスにアクセスできます。requestの型は、データグラムサービスとストリームサービスで異なります。 ストリームサービスではrequestはソケットオブジェクトです。 データグラムサービスではrequestはバイト列とソケットのペアです。
- finish()¶
handle()メソッドより後に呼び出され、何らかの必要なクリーンアップ処理を行います。標準の実装では何も行いません。setup()メソッドが例外を送出した場合、このメソッドは呼び出されません。
- request¶
The new
socket.socketobject to be used to communicate with the client.
- client_address¶
BaseServer.get_request()が返したクライアントのアドレスです。
- server¶
要求の処理に使う
BaseServerオブジェクトです。
- class socketserver.StreamRequestHandler¶
- class socketserver.DatagramRequestHandler¶
これらの
BaseRequestHandlerのサブクラスは、setup()メソッドとfinish()メソッドをオーバーライドし、rfile属性とwfile属性を提供します。- rfile¶
受信した要求を読み取るためのファイルオブジェクトです。
io.BufferedIOBaseの読み取り用インターフェースをサポートします。
- wfile¶
応答を書き込むためのファイルオブジェクトです。
io.BufferedIOBaseの書き込み用インターフェースをサポートします。
バージョン 3.6 で変更:
wfileもio.BufferedIOBaseの書き込み用インターフェースをサポートするようになりました。
使用例¶
socketserver.TCPServer の例¶
サーバサイドの例です:
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
"""
このサーバの要求処理クラス。
サーバへの接続ごとに 1 回インスタンス化される。
handle() メソッドをオーバーライドして、
クライアントとの通信を実装する必要がある。
"""
def handle(self):
# self.request はクライアントと接続された TCP ソケット
pieces = [b'']
total = 0
while b'\n' not in pieces[-1] and total < 10_000:
pieces.append(self.request.recv(2000))
total += len(pieces[-1])
self.data = b''.join(pieces)
print(f"Received from {self.client_address[0]}:")
print(self.data.decode("utf-8"))
# 同じデータを大文字に変えて送り返す
self.request.sendall(self.data.upper())
# このメソッドから戻るとソケットは閉じられる。
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# localhost のポート 9999 にバインドしてサーバを作成する
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# サーバを起動する。
# Ctrl-C でプログラムを中断するまで動き続ける。
server.serve_forever()
別の、ストリーム(標準のファイル型のインターフェースを利用して通信をシンプルにしたファイルライクオブジェクト)を使うリクエストハンドラクラスの例です:
class MyTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
# self.rfile はハンドラーが作成したファイル類似オブジェクト。
# recv() を直接呼ぶ代わりに、例えば readline() を使える。
# 送信側による悪用を防ぐため、10000 バイトに制限する。
self.data = self.rfile.readline(10000).rstrip()
print(f"{self.client_address[0]} wrote:")
print(self.data.decode("utf-8"))
# 同様に、self.wfile はクライアントへの書き込みに使う
# ファイル類似オブジェクト。
self.wfile.write(self.data.upper())
The difference is that the readline() call in the second handler will call
recv() multiple times until it encounters a newline character, while the
first handler had to use a recv() loop to accumulate data until a
newline itself. If it had just used a single recv() without the loop it
would just have returned what has been received so far from the client.
TCP is stream based: data arrives in the order it was sent, but there is no
correlation between client send() or sendall() calls and the number
of recv() calls on the server required to receive it.
クライアントサイドの例:
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# ソケットを作成する (SOCK_STREAM は TCP ソケットを表す)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# サーバに接続してデータを送信する
sock.connect((HOST, PORT))
sock.sendall(bytes(data, "utf-8"))
sock.sendall(b"\n")
# サーバからデータを受信して終了する
received = str(sock.recv(1024), "utf-8")
print("Sent: ", data)
print("Received:", 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(f"{self.client_address[0]} wrote:")
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 は UDP ソケットに使うソケットの種類
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# UDP には接続がないため、connect() は呼び出さない。
# 代わりに sendto() で受信側へ直接データを送信する。
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")
print("Sent: ", data)
print("Received:", received)
この例の出力は、TCPサーバーの例と全く同じようになります。
非同期処理の Mix-in¶
複数の接続を非同期に処理するハンドラを作るには、 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__":
# ポート 0 は、未使用の任意のポートを選ぶことを意味する
HOST, PORT = "localhost", 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
with server:
ip, port = server.server_address
# サーバを実行するスレッドを開始する。
# このスレッドが要求ごとにさらに 1 つスレッドを開始する。
server_thread = threading.Thread(target=server.serve_forever)
# メインスレッドの終了時にサーバスレッドも終了させる
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 プラットフォームでのみ利用可能です。