UDPのサーバーを作ってみる

つづいて、サーバー側を見てみましょう。 こちらは逆であり、クライアント側からのデータを受け取っての処理となります。

UDP echoサーバー

ファイル名はクライアント側にならって、echo-server.pyとします。

 1#!/usr/bin/env python3
 2import socket
 3
 4def main():
 5    # UDPソケットを作る
 6    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
 7        # 今回は、localhostのポート10000で待ち受けます(10000/udp)
 8        server_address = ('localhost', 10000)
 9        sock.bind(server_address)
10
11        print(f'サーバーは {server_address} で待ち受けています')
12
13        while True:
14            # データを受信する
15            data, address = sock.recvfrom(4096)
16            print(f'受信しました: {data} from {address}')
17
18            # 受け取ったデータをそのまま送り返す(エコー)
19            sent = sock.sendto(data, address)
20            print(f'送信しました: {data}')
21
22if __name__ == '__main__':
23    main()

サーバー側でもソケットを作る部分は同じですね。

待ち受け

相手が自身の10000/udpにデータを投げつけてくることになるので、そこに対して待機しておかないといけません。 この行為は、bind(バインド、結びつける)と呼ばれます。 プログラム(プロセス)を指定ポートに結びつけることで、入ってきた情報をそのままプロセスに渡してもらおうというイメージです。

        sock.bind(server_address)

バインド先の指定は、タプルを用いて接続先ホスト(自身の持つIPアドレスのいずれか)とポート番号となります。

注釈

('', 10000)のようにIPアドレス指定部分を空文字列を指定することで、すべてのIPアドレスで待ち受けることができます。

データの受信

届いたデータは、recvfromメソッドを使って受信します。

            print(f'受信しました: {data} from {address}')

recvfromはクライアント側での折り返し受信にて既に説明済みです。送る側も受ける側もおおむね同じ事をしているという所もわかりやすくていいですね。

このコードでは、折り返しの送信を行っているため、受け取ったデータをそのままsock.sendto()にて送り返しています。

実際に走らせてみよう

両ファイルの保存を確認したら、ターミナル上で起動してみます。 今回は2つの端末を使うことになるので、『ターミナルの分割』を使って左右でクライアント・サーバーという形にするといいでしょう。

サーバー側をまず起動してみます。

uv run echo-server.py

すると、以下のメッセージが出て待機状態に入ります。

サーバーは ('localhost', 10000) で待ち受けています

次にクライアント側の起動です。

uv run echo-client.py

こちらは即座に動き、以下のような出力となります。

送信内容: b'Hello, World!'
応答待ち
受信しました: b'Hello, World!'
終了します

そしてサーバー側でも応答が追加されます。

受信しました: b'Hello, World!' from ('127.0.0.1', 35270)
送信しました: b'Hello, World!'

このように、クライアント側から送られたデータを受け取り、そのまま返信していることがわかります。

サーバーが動いていなかったら?

UDPでは相手のサーバーが受付可能なじょうたいであるかの確認は事前には行いません(『投げる』でしたね)。そのため、サーバー側を止めても送信行為は可能です。

  • echo-server.py 側を停止させる(Ctrl+C)

  • echo-client.py 側を起動させる

すると、応答待ちでクライアント側が停止してしまいます。

送信内容: b'Hello, World!'
応答待ち

確認できたら、Ctrl+Cでクライアント側も停止させておきましょう。そのため、クライアント側では送信の可否判定が直接行えないことがわかります。そのため、送信タイムアウトを設けて適当なタイミングでエラー終了とさせておくといいでしょう。

 1--- /home/runner/work/2025-network-doc/2025-network-doc/source/transport/udp-programming/sources/echo/echo-client.py
 2+++ /home/runner/work/2025-network-doc/2025-network-doc/source/transport/udp-programming/sources/echo/echo-client-timeout.py
 3@@ -13,8 +13,13 @@
 4 
 5         # Receive response
 6         print('応答待ち')
 7-        data, server = sock.recvfrom(4096)
 8-        print(f'受信しました: {data}')
 9+        # タイムアウトを1秒に設定
10+        sock.settimeout(1.0)
11+        try:
12+            data, server = sock.recvfrom(4096)
13+            print(f'受信しました: {data}')
14+        except socket.timeout:
15+            print('応答がありませんでした。タイムアウトしました。')
16 
17     print('終了します')
18 

このように、送信の可否を自分できちんと判定する必要があることもまたUDPの特徴となります。