🎉

【Python入門】UNIXドメインソケットで学ぶプロセス間通信の実装例(SOCK_DGRAM編)

に公開

はじめに

以前、以下の記事では、プロセス間通信をUNIXドメインソケット・ソケットタイプ SOCK_STREAM を
使ってクライアント・サーバ簡易プログラムを実装しました。

簡易プログラムを作成

CLI だけで動作するクライアント・サーバを用意しました。
クライアントが送信したデータをサーバが受け取り、加工してレスポンスを返します。

クライアント

  • 文字列をサーバに送信
  • サーバからレスポンスを受け取り、プログラム終了

サーバ

  • クライアントから文字列を受け取り
  • 受け取った文字列に文字列を追加してクライアントにレスポンス
  • レスポンス送信後は他のリクエストを待機

開発環境

  • OS: macOS Sequoia 15.6.1
  • PC: MacBook Pro(M2チップ)
  • エディタ: VS Code
  • 言語: Python 3.13.5
  • 使用ライブラリ: 標準ライブラリのみ(os, json, socket)

ディレクトリ構成

unix-domain-socket/
├── tmp/
├── udp/
│   ├── udp-client.py
│   └── udp-server.py
└── config.json

実装コード

{
    "udp_server_socket_filepath": "/tmp/udp_socket_file",
    "udp_client_socket_filepath": "/tmp/udp_client_socket_file"
}
udp-server.py
import os
import json
import socket

# 1. ソケットの作成
udp_server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)

config = json.load(open('config.json'))
server_address = config['udp_server_socket_filepath']

try:
    os.unlink(server_address)
except FileNotFoundError:
    pass

# 2. ソケットにバインド
udp_server_socket.bind(server_address)

# 3. データ待機
while True:
    print("\nwaiting to receive messages...")

    data_from_client, client_address = udp_server_socket.recvfrom(4096)
    # 受け取ったメッセージをデコード 
    decoded_client_message = data_from_client.decode('utf-8')
    print(f"\nreceived message detail")
    print(f"- client address: {client_address}")
    print(f"- content(byte): {data_from_client}")
    print(f"- content(str): {decoded_client_message}")
    print(f"- size: {len(data_from_client)} bytes")

    # 4. データ送信
    if data_from_client:
        # クライアントから受け取った文字列に文字列を追加
        server_message =  f"Server received ~{decoded_client_message}~"
        # 送信メッセージをエンコード
        encoded_server_message = server_message.encode('utf-8')
        # 送信したバイト数を変数に格納
        sent_server_message_byte = udp_server_socket.sendto(encoded_server_message, client_address)
        print("\nsent message detail")
        print(f"- sent address: {client_address}")
        print(f"- content(byte): {encoded_server_message}")
        print(f"- content(str): {server_message}")
        print(f"- size: {sent_server_message_byte} bytes")
udp-client.py
import os
import json
import socket

# 1. ソケットの作成
udp_client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)

config = json.load(open('config.json'))
server_address = config['udp_server_socket_filepath']
client_address = config['udp_client_socket_filepath']

# ソケットファイルが残っていてもいなくても対応できる
try:
    os.unlink(client_address)
except FileNotFoundError:
    pass

# 2. ソケットのバインド
udp_client_socket.bind(client_address)

# 3. メッセージの送信・サーバからの応答待ち
with udp_client_socket:
    message = input("Enter your message: ").encode('utf-8')
    sent_message_byte = udp_client_socket.sendto(message, server_address)
    print("\nsending message datail")
    print(f"- content(byte): {message}")
    print(f"- size: {sent_message_byte} bytes")

    print("\nwaiting to receive...")
    data_from_server, server_address = udp_client_socket.recvfrom(4096)
    decoded_server_message = data_from_server.decode('utf-8')
    print("\nreceived message datail from server")
    print(f"- server address: {server_address}")
    print(f"- content(byte): {data_from_server}")
    print(f"- content(str): {decoded_server_message}")
    print(f"- size: {len(data_from_server)} bytes")

# 4. ソケット終了    
print("\nclosing socket\n")    

1. recv()

recv() はコネクション型ソケット(TCPなど)で使われるメソッドです。
これは、すでに確立された接続からデータを受け取ります。接続が確立されているため、データの送信元(クライアント)はすでに分かっており、その情報を受け取る必要がありません。

  • 使用するソケットタイプ: socket.SOCK_STREAM
  • 戻り値: 受信したデータ(バイト列)のみ
  • 特徴: データの送信元を特定する必要がない

2. recvfrom()

recvfrom() はコネクションレス型ソケット(UDPなど)で使われるメソッドです。
UDPは「投げっぱなし」の通信なので、サーバーはどのクライアントからデータが送られてきたかを知る必要があります。recvfrom() は、受信したデータに加えて、そのデータの送信元アドレスも一緒に返します。

  • 使用するソケットタイプ: socket.SOCK_DGRAM
  • 戻り値: (受信したデータ, 送信元のアドレス)のタプル
  • 特徴: データの送信元アドレスを一緒に取得する

3. sendto

socket.sendto(bytes, address)
socket.sendto(bytes, flags, address)
ソケットにデータを送信します。このメソッドでは接続先を address で指定するので、接続済みではいけません。オプション引数 flags の意味は、上記 recv() と同じです。戻り値として、送信したバイト数を返します。(address のフォーマットはアドレスファミリによって異なります --- 前述。)
https://docs.python.org/ja/3.13/library/socket.html#socket.socket.sendto より引用

実際の動作

1. サーバ起動

2. クライアント起動

3. クライアントがメッセージを入力・送信

4. サーバがデータ受け取り

5. サーバがレスポンス送信

6. クライアントがデータを受け取り・終了

7. サーバは引き続きリクエスト待機

実際に動作することが確認できます。
この後、クライアントを再度起動して同じ動作を行なった場合も結果は同じでした。

補足

UNIXドメインソケットはファイルパスを「名前解決」に使用しますが、データ転送自体はカーネル内部で行われます。実際にファイルへ書き込まれるわけではない点に注意しましょう。

まとめ

簡易クライアント・サーバプログラムを通じて、データの送受信について学びました。
コネクションレス型かどうかで相手のアドレスを明示的に送信する必要があるかどうかがわかりました。
実際にお手元で試してどのような動作が実現できるのか追体験してみてください。

今回の内容が何かのお役に立てれば幸いです。

最後までお読みいただき、ありがとうございました。

参考URL

https://recursionist.io/dashboard/course/31/lesson/1099

https://docs.python.org/ja/3/howto/sockets.html

https://bombrary.github.io/blog/posts/socket02-python/

https://docs.python.org/ja/3.13/library/socket.html#socket.socket.sendto

Discussion