Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonでマルチスレッドを使ったソケットプログラミングの実装方法を解説

マルチスレッドの基本概念

マルチスレッドは、ほぼすべての現代的なプログラミング言語に共通する中核的な概念です。特にPythonは、スレッドをシンプルに実装できるため、マルチスレッド処理と非常に相性の良い言語といえます。

スレッドとは、プログラム内部にある「サブプログラム」であり、コードの他の部分から独立して実行できる単位のことです。スレッドは同一のコンテキスト内で動作し、メモリなどプログラムの実行リソースを共有します。

そして、1つのプロセスの中で複数のスレッドを同時に実行することを「マルチスレッド」と呼びます。

Pythonでスレッドを実装するためのモジュール

Pythonでは、プログラム内にスレッドを実装するために、主に次の2つのモジュールが提供されています。

  • thread モジュール(Python 2.x)/ _thread モジュール(Python 3.x)
  • threading モジュール

thread(_thread)モジュールは関数ベースでスレッドを生成するのに対し、threadingモジュールはオブジェクト指向のアプローチでスレッドを扱える点が大きな違いです。現在の開発では、より高機能で扱いやすいthreadingモジュールを使うのが一般的です。

基本構文

_thread.start_new_thread(func, args[, kwargs])

上記のコードは新しいスレッドを起動し、その識別子を返します。第1引数にはスレッドが実行する関数 func を指定し、第2引数には位置引数をまとめたタプルを渡します。省略可能な kwargs 引数では、キーワード引数の辞書を指定できます。関数の処理が完了すると、スレッドは自動的に終了します。

クライアント・サーバーアプリケーションの実装例

ここでは、マルチスレッドを活用したクライアント・サーバーアプリケーションの基本的な例を紹介します。クライアントはソケット接続を確立してサーバーへメッセージを送信し、サーバーはそれを受けて応答を返します。

引数なしで実行した場合、このプログラムはTCPソケットサーバーとして起動し、127.0.0.1 のポート8000で接続を待ち受けます。

クライアント側のコード(client_thread1.py)

import socket
import sys

def main():
    soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    host = "127.0.0.1"
    port = 8000
    try:
        soc.connect((host, port))
    except:
        print("Connection Error")
        sys.exit()
    print("Please enter 'quit' to exit")
    message = input(" -> ")
    while message != 'quit':
        soc.sendall(message.encode("utf8"))
        if soc.recv(5120).decode("utf8") == "-":
            pass  # 何もしない(null操作)
        message = input(" -> ")
    soc.send(b'--quit--')

if __name__ == "__main__":
    main()

サーバー側のコード(server_thread1.py)

import socket
import sys
import traceback
from threading import Thread

def main():
    start_server()

def start_server():
    host = "127.0.0.1"
    port = 8000  # 任意の非特権ポート
    soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    soc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    print("Socket created")
    try:
        soc.bind((host, port))
    except:
        print("Bind failed. Error : " + str(sys.exc_info()))
        sys.exit()
    soc.listen(6)  # 最大6件のリクエストをキューに保持
    print("Socket now listening")
    # 無限ループ:リクエストごとにリセットしない
    while True:
        connection, address = soc.accept()
        ip, port = str(address[0]), str(address[1])
        print("Connected with " + ip + ":" + port)
        try:
            Thread(target=clientThread, args=(connection, ip, port)).start()
        except:
            print("Thread did not start.")
            traceback.print_exc()
    soc.close()

def clientThread(connection, ip, port, max_buffer_size=5120):
    is_active = True
    while is_active:
        client_input = receive_input(connection, max_buffer_size)
        if "--QUIT--" in client_input:
            print("Client is requesting to quit")
            connection.close()
            print("Connection " + ip + ":" + port + " closed")
            is_active = False
        else:
            print("Processed result: {}".format(client_input))
            connection.sendall("-".encode("utf8"))

def receive_input(connection, max_buffer_size):
    client_input = connection.recv(max_buffer_size)
    client_input_size = sys.getsizeof(client_input)
    if client_input_size > max_buffer_size:
        print("The input size is greater than expected {}".format(client_input_size))
    decoded_input = client_input.decode("utf8").rstrip()
    result = process_input(decoded_input)
    return result

def process_input(input_str):
    print("Processing the input received from client")
    return "Hello " + str(input_str).upper()

if __name__ == "__main__":
    main()

実行手順と動作確認

まず、ターミナルでサーバープログラムを実行します。

python server_thread1.py
Socket created
Socket now listening

サーバーが待ち受け状態になったら、複数のターミナルを開いてクライアントを起動してみましょう。

1つ目のクライアント:

python client_thread1.py
Enter 'quit' to exit
-> Zack
->

別のターミナルで2つ目のクライアント:

python client_thread1.py
Enter 'quit' to exit
-> Python
-> quit

さらに別のターミナルで3つ目のクライアント:

python client_thread1.py
Enter 'quit' to exit
-> world!
-> Anothny
->

すると、サーバー側のウィンドウには次のような出力が表示されます。

Socket created
Socket now listening
Connected with 127.0.0.1:50275
Processing the input received from client
Processed result: Hello ZACK
Connected with 127.0.0.1:50282
Processing the input received from client
Processed result: Hello PYTHON
Processing the input received from client
Client is requesting to quit
Connection 127.0.0.1:50282 closed
Connected with 127.0.0.1:50285
Processing the input received from client
Processed result: Hello WORLD!
Processing the input received from client
Processed result: Hello ANOTHNY

まとめ

このように、各クライアントの接続ごとに個別のスレッドを生成することで、サーバーは複数のクライアントを同時に処理できるようになります。マルチスレッドは、複数のソケット接続や複数クライアントを効率的に捌くための、最も一般的かつ強力な手法のひとつです。チャットアプリケーションやリアルタイム通信システムなどを構築する際の基礎となる技術なので、ぜひ実際にコードを動かして挙動を確認してみてください。

  1. 【Python入門】メタクラスによるメタプログラミングの基本と実践例

    メタプログラミングとは、プログラムが自分自身を操作したり、自身についての知識を持ったりするプログラミング手法のことを指します。Pythonでは「メタクラス」と呼ばれる特殊なクラスを使うことで、クラスに対するメタプログラミングを実現できます。メタクラスによるメタプログラミングでは、既存のコードを修正・ラップ・生成することでコードを操作する関数やクラスを構築します。メタプログラミングの主な機能は以下の通りです。メタクラスデコレータクラスデコレータメタクラスとは何かメタクラスを簡潔に定義するなら、「クラスを作り出すクラス」です。Pythonはオブジェクト指向言語であり、すべてがオブジェクトとして扱わ

  2. PythonのTurtleグラフィックス入門!基本メソッド一覧と図形描画サンプルコード集

    Turtle(タートル)はPythonに標準搭載された特殊な機能で、専用の描画ウィンドウ上に簡単に図形を描くことができるモジュールです。 使い方はとてもシンプルです。まずturtleモジュールをインポートし、次にウィンドウ(Screen)を作成、その後タートルオブジェクトを生成します。あとは各種メソッドを呼び出すだけで、キャンバス上に自由に描画できます。この記事では、よく使われるメソッドの一覧表と、正方形・星・六角形などの具体的な描画例をサンプルコード付きで解説します。 主なTurtleメソッド一覧 メソッド パラメータ 説明 Turtle() なし 新しいタートルオブジェクトを作成