【Python】ソケット通信サンプル 〜プロセス間通信入門#1〜

Pythonでプロセス間通信の勉強を始めました。
第一弾は通常のソケット通信です。

概要

ソケット通信自体はご存知の方が多いかと思いますが、今回はそれをプロセス間で実行していきます。

コード自体は通常のソケット通信と同じで、IPアドレスがローカル(自PC)になるだけです。
(簡単!!)

ソケット通信は広く使われている技術であり、Pythonに限らず書籍・ネット記事が多いので、プロセス間通信の中では一番とっつきやすいと思います。


今回は、一般的なサーバ↔︎クライアントの通信処理を作っていきます。

サーバ↔︎クライアント間のデータのやりとりは、可変長データを簡単に扱えるように以下の形式とします。
(超簡易プロトコル)

①データ本体のサイズを固定サイズで送信

②①で送信したサイズの分だけデータ本体を送信

(繰り返し)

※エラーチェックとか無いので実務では使えないケースが多いと思います。

サーバ側コード

サーバ側のコードは以下です。

クライアントからのリクエストを待ち続け、クライアントのリクエストを受信したら、同じデータをそのまま返すという簡単なサンプルになっています。

import socketserver
from struct import unpack

"""
今回サーバ↔︎クライアント間で
やりとりするデータの構造

+-----------------------------+
| ヘッダー                            |
+-----------------------------+
| 内容: データサイズ            |
| サイズ: 4byte(固定)        |
+-----------------------------+
+-----------------------------+
| データ                               |
+-----------------------------+
| 内容: 実際のデータ           |
| サイズ: 上記指定のサイズ |
+-----------------------------+
"""
# ヘッダサイズ (固定長)
HEADER_SIZE = 4

# サーバのアドレスとポート
ADDR = "127.0.0.1"
PORT = 50550


class MyTCPHandler(socketserver.BaseRequestHandler):
    """サーバに届いたリクエストを処理するクラス."""

    def handle(self) -> None:
        """
        クライアントからのリクエストを処理する関数.

        クライアントからのリクエストを受信する毎に実行される
        """
        # ヘッダ受信
        _header = self.request.recv(HEADER_SIZE)

        # ヘッダからデータサイズを取得 (受信データはbytesなのでintとして扱うためにunpackが必要)
        data_size = unpack('!I', _header)[0]
        print(data_size)

        # データ本体の受信
        _data = self.request.recv(data_size)
        print(_data)

        # 今回はサンプルなので受信したデータをそのまま返す
        # ヘッダ送信
        self.request.sendall(_header)
        # データ送信
        self.request.sendall(_data)


def start_server() -> None:
    """サーバ起動."""
    with socketserver.TCPServer((ADDR, PORT), MyTCPHandler) as s:
        # サーバとしてクライアントのリクエストを待ち続ける
        s.serve_forever()


if __name__ == '__main__':
    start_server()

クライアント側コード

クライアント側のコードは以下です。

コンソール入力の文字列をサーバに送信

サーバからのレスポンスを受信してprint()

と、こちらも簡単なサンプルになっています。

import fileinput
import socket
from struct import pack, unpack

# ヘッダサイズ (固定長)
HEADER_SIZE = 4

# サーバのアドレスとポート (サーバ側の設定と合わせること)
ADDR = "127.0.0.1"
PORT = 50550


def tcp_client(data: str) -> None:
    """クライアント処理."""

    # ソケット作成 (INETドメインのTCPソケット)
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:

        # サーバに接続
        sock.connect((ADDR, PORT))

        # ヘッダ送信
        # (データ本体の長さを4byteのbytesオブジェクトに変換して送信)
        sock.sendall(pack('!I', len(data)))

        # データ送信
        # (データ本体を文字コードutf-8としてbytesオブジェクトに変換)
        sock.sendall(bytes(data, 'utf-8'))

        # サーバからのレスポンスヘッダ受信
        received_header = sock.recv(HEADER_SIZE)

        # ヘッダからデータサイズを取得
        data_size = unpack('!I', received_header)[0]
        print(data_size)

        # データ本体の受信
        received = sock.recv(data_size)

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


if __name__ == '__main__':
    for line in fileinput.input():
        # コンソール入力の改行毎にクライアント処理を実行
        tcp_client(line)

参考

Socketサーバ
https://docs.python.org/ja/3/library/socketserver.html

Socket通信
https://docs.python.org/ja/3/library/socket.html

コンソール入力待ち

コメント

タイトルとURLをコピーしました