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
コンソール入力待ち
コメント