📂

【Python】プロセス間通信の基本(ソケット)

に公開

概要

以前、パイプについての記事を書きました。

https://zenn.dev/shimiyu/articles/44eee5d0e76276

今回は、パイプよりもさらに自由で柔軟な通信を可能にする ソケット について取り上げます。

ソケットとは

ソケットは、パイプと同じようにプロセス間でデータをやり取りできる仕組みです。Pythonでは、パイプが read / write を使って通信するのに対し、ソケットでは send(またはsendall) / recv を使って同様にデータの送受信ができます。

しかし、ソケットにはパイプとは異なる次のような特徴があります。

  • 同一マシン内だけでなく、ネットワーク越しの通信が可能
  • 双方向通信が可能(1本で送受信可)
    • ※ パイプで双方向通信をするにはパイプが2本必要です

パイプとソケットの比較

特徴 無名パイプ(pipe) 名前付きパイプ(FIFO) ソケット
通信範囲 同一マシン内のみ 同一マシン内のみ 同一マシン+ネットワーク越し
プロセス関係 親子プロセス間 任意のプロセス間 任意のプロセス間
接続の方法(Python) pipe() で生成 mkfifo() でパスを指定 bind()connect() を使用
通信の方向 一方向(通常) 一方向(通常) 双方向が基本

ソケットの使い所

上で見たように、ソケットは、パイプよりも自由度が高く、柔軟な通信が可能です。

具体的には、次のようなシチュエーションでよく使われます。

  • Webサーバーとブラウザのように、異なるマシン上のプロセス間の通信
  • 独立したプロセス同士による非同期なやり取り
  • 複数のクライアントからの接続を受け付けるサーバ

もちろん、パイプで上記を実現できることもあります。しかし、パイプには

  • 親子関係に縛られる
  • 基本的に一方向通信
  • 複数プロセスとのやり取りが難しい

など、拡張性の面で制限が多いです。

こうした制限を超えて、柔軟なプロセス間通信を実現したい場合、ソケットはとても便利です。

ソケット通信の基本(Python)

Pythonでソケット通信を行う場合、socketモジュールを使います。以下の図は、TCP通信におけるサーバとクライアントの基本的なやり取りの流れです。

ソケット通信のサンプルコード

ここでは、ソケットを使った簡単なサーバとクライアント間の通信を試してみます。せっかくなので、同じネットワーク上の別マシン同士で通信できる構成を試してみたいと思います。

以下は、TCPソケットで接続を受け付け、受け取ったメッセージに日時を付与して返すシンプルなサーバとそれを利用するクライアントの実装です。

サーバ側の実装

TCPソケットで接続を受け付け、受け取ったメッセージに日時を付与して返すサーバの実装です。

server.py
import socket
from datetime import datetime

HOST = '0.0.0.0'
PORT = 12345

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock:
    server_sock.bind((HOST, PORT))
    server_sock.listen()

    print(f'Listening on {HOST}:{PORT}...')
    conn, addr = server_sock.accept()
    with conn:
        print(f'Connected by {addr}')
        while True:
            data = conn.recv(1024)
            if not data:
                break
            message = data.decode()
            now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            # 日時付きで返信を作成
            response = f'[{now}] {message}'
            conn.sendall(response.encode())

サーバ側のローカルIPアドレスを確認

クライアント側から接続するには、サーバ側のローカルIPアドレスを知る必要があります。

macOSの場合、以下のように ifconfig コマンドで確認できます。

% ifconfig
# ...略
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        # ...略
        inet 192.168.10.4 netmask 0xffffff00 broadcast 192.168.10.255
# ...略

この場合、192.168.10.4がクライアントから接続すべきアドレスです。

クライアント側の実装

続いて、サーバを利用するクライアントの実装です。

ソケットを使って接続し、"Hello, server!" というメッセージを送信します。サーバはこのメッセージに日時を付けて返信しますので、それを受け取って表示するという流れです。

client.py
import socket

HOST = '192.168.10.4' # サーバ側のローカルIPアドレスを指定する
PORT = 12345

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_sock:
    client_sock.connect((HOST, PORT))
    client_sock.sendall(b'Hello, server!')
    data = client_sock.recv(1024)

print(f'Received: {data.decode()}')

通信を試す

同じネットワークに接続する2台のPCを用意します。

まず、1台目でサーバを起動します。

すると、サーバが起動し、接続待機状態(Listen)になりました。

% python3 server.py
Listening on 0.0.0.0:12345...

続いて、同一ネットワークにあるもう1台のPCでクライアント側のPythonコードを実行します。

% python3 client.py
Received: [2025-03-30 15:57:02] Hello, server!

すると、ちゃんと日時が付与された状態でメッセージが返ってきました。

サーバ側のターミナルを見ると、Connected by ('192.168.10.8', 60712)が表示され、処理がcloseしていることが分かります。

% python3 server.py
Listening on 0.0.0.0:12345...
Connected by ('192.168.10.8', 60712)

最後に

今回は、PythonでTCPソケットを使って、ネットワーク越しに2台のPC間で通信するシンプルなサンプルを実装しました。

プロセス、パイプに引き続き、超基本的な内容ですが、実際に動かしてみることでだいぶ理解が深まったように感じました。

Discussion