PythonでTCPのソケット通信を学ぶと、サーバー側には「接続を待ち受けるソケット」と「接続した相手と通信するソケット」が登場します。
一方、UDPのサーバーでは、1つのソケットを使ってデータを直接送受信します。
さらに、TCPには「データを順番どおりに届ける」という特徴もあります。ここで、次のような疑問が浮かびました。
- TCPサーバーには、なぜ2種類のソケットが必要なのか
- クライアント側は、なぜ1つのソケットで通信できるのか
- UDPでは、なぜ接続相手ごとのソケットを作らないのか
- 接続相手ごとのソケットを作ることと、データの順番を保証することは関係しているのか
この記事では、Pythonのコードをもとに、これらを一つずつ整理します。
先に結論
最初に、重要なポイントをまとめます。
| 項目 | 役割 |
|---|---|
| TCPサーバーの待ち受け用ソケット | 新しいTCP接続を受け付ける待ち受け用ソケット |
| TCPサーバーの接続済みソケット | 特定のクライアントと通信する接続済みソケット |
| TCPクライアントのソケット | 特定のサーバーと接続・通信するソケット |
| TCPの順序保証 | シーケンス番号などを使い、データを正しい順番に整える仕組み |
TCPサーバーで使う2つのオブジェクトは、どちらもソケットです。ただし、役割が異なります。
また、接続相手ごとのソケットを作る目的と、データを並べ替える目的は別です。
接続済みソケットは「誰との接続なのか」を分けるもの。
順序保証は「その接続のデータをどの順番で渡すのか」を管理するもの。
ソケットとは
ソケットとは、アプリケーションがネットワークを通じてデータを送受信するための出入り口です。
Pythonでは、次のコードでソケットオブジェクトを作成します。
import socket
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
AF_INET:IPv4を使用するSOCK_STREAM:TCPを使用する
この時点のtcp_socketは、TCPで通信するためのソケットです。その後にどのメソッドを使うかによって、サーバー側・クライアント側の役割が決まります。
教材やサンプルコードでは、ソケットをs、接続をconnと短く書くことがあります。しかし、どちらもPythonで決められた特別な名前ではなく、開発者が自由に付けた変数名です。
| 短い変数名 | この記事で使う名前 | 意味 |
|---|---|---|
s(サーバー側) |
listening_socket |
接続を待ち受けるソケット |
s(クライアント側) |
client_socket |
サーバーと接続・通信するソケット |
conn |
connection_socket |
接続した相手と通信するソケット |
addr |
client_address |
接続相手のIPアドレスとポート番号 |
この記事では役割が伝わる変数名を使います。教材でsやconnが出てきたら、上の表のように読み替えてください。
TCPサーバーの2つのソケット
次のコードは、TCPサーバーの簡単な例です。
import socket
listening_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listening_socket.bind(("127.0.0.1", 60001))
listening_socket.listen(1)
connection_socket, client_address = listening_socket.accept()
connection_socket.send(b"Hello World!")
connection_socket.close()
listening_socket.close()
待ち受け用ソケット
listening_socket.bind(("127.0.0.1", 60001))
listening_socket.listen(1)
bind()でIPアドレスとポート番号を紐づけ、listen()で接続待ちの状態にします。
ここでのlistening_socketは、新しいクライアントからの接続を受け付ける待ち受けソケット(リスニングソケット)です。
listen(1)の1は、このプログラムが同時に処理できるリクエスト数ではなく、接続を待たせておくキューの大きさです。
接続済みソケット
connection_socket, client_address = listening_socket.accept()
クライアントが接続すると、accept()は次の2つを返します。
| 戻り値 | 内容 |
|---|---|
connection_socket |
接続したクライアントと通信するための新しいソケット |
client_address |
クライアントのIPアドレスとポート番号 |
変数が自分でソケットを作っているのではありません。accept()が返した接続済みソケットを、connection_socketという変数で受け取っています。
実際のデータ通信には、この接続済みソケットを使います。
connection_socket.send(b"Hello World!")
なぜTCPサーバーは接続済みソケットを作るのか
サーバーは、複数のクライアントから接続される可能性があります。
そのため、次の2つの仕事を分ける必要があります。
- 新しい接続を受け付ける
- 接続済みのクライアントと通信する
待ち受け用ソケットを受付窓口として残し、接続したクライアントごとに接続済みソケットを用意します。
待ち受け用ソケット
├─ 接続済みソケット1 ⇄ クライアント1
├─ 接続済みソケット2 ⇄ クライアント2
└─ 接続済みソケット3 ⇄ クライアント3
それぞれの役割は次のとおりです。
- 待ち受け用ソケット:新しい接続を受け付ける
- 接続済みソケット1:クライアント1と通信する
- 接続済みソケット2:クライアント2と通信する
- 接続済みソケット3:クライアント3と通信する
connection_socket.close()を実行しても、終了するのはそのクライアントとの接続だけです。待ち受け用ソケットを閉じなければ、新しい接続を引き続き受け付けられます。
受付に例えるなら、待ち受け用ソケットは総合受付、接続済みソケットは受付後に割り当てられる個別の担当窓口です。
クライアント側はなぜ1つのソケットでよいのか
クライアント側のコードを見てみます。
import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(("127.0.0.1", 60001))
data = client_socket.recv(4096)
print(data)
client_socket.close()
クライアント側では、同じclient_socketを使って次の処理を行います。
- サーバーに接続する
- データを送受信する
- 接続を終了する
client_socket.connect(...) # 接続
client_socket.recv(4096) # 受信
client_socket.close() # 終了
クライアント側のソケットは、最初から特定のサーバーとの1つの接続を担当します。そのため、サーバーのように「受付用」と「個別通信用」を分ける必要がありません。
実際に通信する組み合わせは、次のようになります。
サーバー側の接続済みソケット ⇄ クライアント側のソケット
サーバー側の待ち受け用ソケットと接続済みソケットが、お互いにデータを送受信するわけではありません。
UDPではなぜ接続済みソケットを作らないのか
UDPサーバーは、次のように記述できます。
import socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.bind(("127.0.0.1", 60002))
data, client_address = udp_socket.recvfrom(4096)
print(data, client_address)
udp_socket.close()
UDPでは、TCPのような次の処理がありません。
listening_socket.listen(...)
listening_socket.accept()
その代わり、1つのudp_socketで直接データを受信します。
data, client_address = udp_socket.recvfrom(4096)
UDPはコネクションレスの通信です。通信前にクライアントと継続的な接続を確立しないため、クライアント専用の接続済みソケットも作られません。
どのクライアントから届いたデータなのかは、recvfrom()が返すclient_addressで確認します。
クライアント1 ──データ──→ UDPサーバーのソケット
クライアント2 ──データ──→ UDPサーバーのソケット
クライアント3 ──データ──→ UDPサーバーのソケット
サーバーから返信する場合も、client_addressを宛先に指定します。
data, client_address = udp_socket.recvfrom(4096)
udp_socket.sendto(b"Hello Client!", client_address)
TCPが相手ごとに専用の電話回線をつなぐ通信だとすれば、UDPは宛先を書いた手紙をその都度送る通信に近いイメージです。
TCPはなぜ順番どおりに届くのか
ネットワーク上では、分割されたデータが送信順とは異なる順番で届くことがあります。
TCPでは、送るデータのバイト位置をシーケンス番号で管理します。
| シーケンス番号 | データ |
|---|---|
| 1001 | Hello(5バイト) |
| 1006 | 半角スペース(1バイト) |
| 1007 | World(5バイト) |
データが異なる順番で到着しても、受信側のTCP処理がシーケンス番号を確認して並べ直します。
到着した順番:3 → 1 → 2
並べ直した後:1 → 2 → 3
TCPでは、主に次のような仕組みが働いています。
| 仕組み | 役割 |
|---|---|
| シーケンス番号 | データのバイト位置を管理する |
| ACK(確認応答) | どこまで受信できたかを送信側へ伝える |
| 再送制御 | 届かなかったデータを再送する |
| 受信バッファ | データがそろうまで一時的に保管する |
これらはPythonのコードで自分が直接並べ替えているのではなく、主にOS内部のTCP機能が処理しています。
connection_socket.recv()は、TCPが順番を整えたデータをアプリケーションから読み取るための窓口です。
UDPにも番号を付ければよいのでは?
UDPで送るデータに、自分で順番を表す番号を付けることはできます。
udp_socket.sendto(b"1:Hello", server_address)
udp_socket.sendto(b"2: ", server_address)
udp_socket.sendto(b"3:World", server_address)
受信側で番号を確認すれば、3 → 1 → 2の順に届いたデータを、1 → 2 → 3に並べ直せます。
ただし、UDP自身は並べ替えも再送もしてくれません。アプリケーション側で、次の処理を実装する必要があります。
- 番号を付ける
- 順番が違うデータを一時保存する
- 欠けたデータを判定する
- 再送を要求する
- 送信済みデータを保存しておく
- 重複したデータを除外する
- タイムアウトを管理する
ここまで実装すると、TCPが持つ機能を一部自作している状態になります。
一方で、UDP上に独自の制御を作る意味もあります。オンラインゲームなら、チャットは再送する一方、古くなったキャラクターの位置情報は再送しない、といった調整ができます。
接続済みソケットと順序保証は別の問題
ここが今回の最も重要なポイントです。
| 仕組み | 解決する問題 |
|---|---|
| 接続済みソケット | どのクライアントとのTCP接続なのかを区別する |
| シーケンス番号・ACK・再送制御 | データを正しい順番で確実に渡す |
接続済みソケットがデータを並べ替えているわけではありません。
より正確には、OS内部のTCP機能が接続ごとに順番を管理し、整えたデータをrecv()から受け取れるようにしています。
つまり、両方ともTCPに関係する仕組みですが、目的は別です。
誰との接続なのかを分ける
↓
接続済みソケット
その接続のデータの順番を整える
↓
シーケンス番号・ACK・再送制御
TCPとUDPの使い分け
| 比較 | TCP | UDP |
|---|---|---|
| 通信方式 | コネクション指向 | コネクションレス |
| 接続処理 | あり | なし |
| 到達保証 | あり | なし |
| 順序保証 | あり | なし |
| 再送制御 | あり | なし |
| 向いている通信 | 正確さ・確実性が重要 | 速さ・リアルタイム性が重要 |
TCPは、データが欠けたり順番が変わったりすると困る通信に向いています。
- WebサイトやWeb API
- メール
- ファイル転送
- SSH
- データベース通信
- 注文や決済
UDPは、多少データが欠けても、古いデータを待つより最新データを早く届けたい通信に向いています。
- 音声通話
- ライブ配信
- オンラインゲームの位置情報
- DNS
- 時刻同期
判断の目安は次のとおりです。
1つも欠けず、順番どおりに届けたい → TCP
少し欠けても、今すぐ届けることを優先したい → UDP
なお、HTTP/3で利用されるQUICはUDPを土台にしていますが、単純なUDP通信ではありません。UDP上に再送制御や暗号化などの仕組みを実装しています。
まとめ
- TCPサーバーでは、待ち受け用と接続後の通信用という2種類のソケットを使う
- 待ち受け用ソケットは、新しい接続を受け付ける
- 接続済みソケットは、特定のクライアントと通信する
- クライアント側のソケットは、特定のサーバーとの接続と通信の両方を担当する
- UDPは接続を確立しないため、接続相手ごとのソケットを作らない
- TCPの順序保証は、シーケンス番号・ACK・再送制御などによって実現される
- 接続済みソケットを作る目的と、データの順番を保証する目的は別である
最後に、一文で整理します。
接続済みソケットは「誰と通信しているのか」を表し、TCPの順序制御は「その相手から届くデータをどの順番で渡すのか」を管理する。

コメント