Skip to content
Published on

[コンピュータネットワーク] 07. ソケットプログラミング:TCPとUDP

Authors

本記事は James Kurose, Keith Ross 著 Computer Networking: A Top-Down Approach (6th Edition) の教科書を基にまとめた内容です。


1. ソケットプログラミングの概要

ネットワークアプリケーションは2つのプロセス(クライアント、サーバー)間の通信で構成される。この通信の接点が**ソケット(socket)**である。

┌──────────────┐                    ┌──────────────┐
│ アプリケーション│                    │ アプリケーション│
│  プロセス      │                    │  プロセス      │
│      │        │                    │      │        │
│  ソケット(ドア)│                    │  ソケット(ドア)│
├──────┼────────┤    インターネット    ├──────┼────────┤
│ トランスポート層│ ←═════════════> │ トランスポート層│
└──────────────┘                    └──────────────┘

ソケットはアプリケーション層とトランスポート層の間の**インタフェース(API)**である。

1.1 2つのトランスポートサービス

サービスTCPUDP
接続コネクション指向コネクションレス
信頼性信頼性あり(順序保証、再送)信頼性なし(損失あり)
フロー制御ありなし
輻輳制御ありなし
オーバーヘッド高い低い

2. UDPソケットプログラミング

2.1 UDP通信の特徴

  • 接続設定なし:データ送信前にハンドシェイクが不要
  • 各データグラムに宛先IPとポート番号を明示的に添付
  • パケット順序保証なし、パケット損失の可能性あり

2.2 UDPクライアント-サーバー相互作用フロー

    サーバー                          クライアント
    ────                              ────────
  ソケット作成                         ソケット作成
  (serverSocket)                      (clientSocket)
       │                                  │
  アドレスバインド                         │
  (IP, ポート)                            │
       │                                  │
  受信待機 ←──── データグラム送信 ──── メッセージ送信
  (recvfrom)    (宛先IP:ポート)      (sendto)
       │                                  │
  データ処理                           受信待機
       │                             (recvfrom)
  応答送信 ────── データグラム ──────→ 応答受信
  (sendto)                                │
       │                             ソケット閉じ

2.3 UDPサーバーコード(Python)

from socket import *

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_DGRAM)
serverSocket.bind(('', serverPort))
print("UDP server ready. Port:", serverPort)

while True:
    # Receive message from client
    message, clientAddress = serverSocket.recvfrom(2048)
    print(f"Received: {message.decode()} from {clientAddress}")

    # Convert to uppercase and respond
    modifiedMessage = message.decode().upper()
    serverSocket.sendto(modifiedMessage.encode(), clientAddress)

コード解説

socket(AF_INET, SOCK_DGRAM)
  AF_INET: IPv4
  SOCK_DGRAM: UDPソケット

bind(('', serverPort))
  '': すべてのインタフェースで受信
  serverPort: バインドするポート番号

recvfrom(2048)
  最大2048バイト受信
  返り値: (データ, クライアントアドレス)

sendto(data, address)
  データを指定されたアドレスに送信

2.4 UDPクライアントコード(Python)

from socket import *

serverName = '127.0.0.1'
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_DGRAM)

message = input("Enter a lowercase sentence: ")
clientSocket.sendto(message.encode(), (serverName, serverPort))

# Receive server response
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
print("Server response:", modifiedMessage.decode())

clientSocket.close()

実行例

# Server terminal
$ python UDPServer.py
UDP server ready. Port: 12000
Received: hello world from ('127.0.0.1', 54321)

# Client terminal
$ python UDPClient.py
Enter a lowercase sentence: hello world
Server response: HELLO WORLD

3. TCPソケットプログラミング

3.1 TCP通信の特徴

  • コネクション指向:データ転送前に必ずTCP接続を確立
  • ウェルカム(welcome)ソケット接続ソケットの区別
  • バイトストリームベース(メッセージ境界なし)
  • 信頼性のある転送:順序保証、再送

3.2 TCPクライアント-サーバー相互作用フロー

    サーバー                          クライアント
    ────                              ────────
  ウェルカムソケット作成                     │
  (serverSocket)                           │
       │                                   │
  アドレスバインド                           │
  listen()                                 │
       │                              ソケット作成
  accept() 待機 ←── TCP接続要求 ── connect()
       │          (3-way handshake)      │
  接続ソケット作成                          │
  (connectionSocket)                       │
       │                                   │
  recv() ←──────── データ ──────── send()
       │                                   │
  データ処理                                │
       │                                   │
  send() ────────── 応答 ──────────→ recv()
       │                                   │
  接続ソケット閉じ                      ソケット閉じ
  (connectionSocket.close())               │
  次の接続待機...

核心:2種類のソケット

サーバー側のソケット:

  1. ウェルカムソケット(Welcome/Listening Socket)
     - serverSocket:クライアントの接続要求を受け付けるドア
     - accept()呼び出し時に待機

  2. 接続ソケット(Connection Socket)
     - connectionSocket:特定クライアントとの専用ソケット
     - accept()が返す新しいソケット
     - データ送受信に使用

3.3 TCPサーバーコード(Python)

from socket import *

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(1)
print("TCP server ready. Port:", serverPort)

while True:
    # Accept client connection (blocking)
    connectionSocket, clientAddr = serverSocket.accept()
    print(f"Connection established: {clientAddr}")

    # Receive data
    message = connectionSocket.recv(1024).decode()
    print(f"Received: {message}")

    # Convert to uppercase and respond
    capitalizedMessage = message.upper()
    connectionSocket.send(capitalizedMessage.encode())

    # Close connection socket (welcome socket remains open)
    connectionSocket.close()

コード解説

socket(AF_INET, SOCK_STREAM)
  SOCK_STREAM: TCPソケット

listen(1)
  最大1つの待機接続を許可
  (バックログキューサイズ)

accept()
  接続要求が来るまでブロッキング
  返り値: (新しい接続ソケット, クライアントアドレス)

recv(1024)
  最大1024バイト受信
  TCPはバイトストリームのため境界なし

send(data)
  接続されたソケットでデータ送信
  宛先アドレス指定不要(すでに接続済み)

3.4 TCPクライアントコード(Python)

from socket import *

serverName = '127.0.0.1'
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_STREAM)

# Establish TCP connection (3-way handshake)
clientSocket.connect((serverName, serverPort))

message = input("Enter a lowercase sentence: ")
clientSocket.send(message.encode())

# Receive server response
modifiedMessage = clientSocket.recv(1024)
print("Server response:", modifiedMessage.decode())

clientSocket.close()

実行例

# Server terminal
$ python TCPServer.py
TCP server ready. Port: 12000
Connection established: ('127.0.0.1', 54322)
Received: hello tcp world

# Client terminal
$ python TCPClient.py
Enter a lowercase sentence: hello tcp world
Server response: HELLO TCP WORLD

4. UDP vs TCPソケットプログラミングの比較

4.1 コードレベルの違い

項目UDPTCP
ソケットタイプSOCK_DGRAMSOCK_STREAM
接続設定なしconnect() / accept()
データ送信sendto(data, addr)send(data)
データ受信recvfrom(bufsize)recv(bufsize)
アドレス指定毎回接続時に1回だけ
サーバーソケット1つのみウェルカム + 接続ソケット

4.2 フロー比較ダイアグラム

UDP:                              TCP:
────                              ────
Server: socket()                  Server: socket()
Server: bind()                    Server: bind()
                                  Server: listen()
Client: socket()                  Server: accept() (blocking)
Client: sendto() ──────────>
                                  Client: socket()
Server: recvfrom()                Client: connect() ──────>
Server: sendto() ──────────>                   3-way handshake
                                  Server: (new connection socket returned)
Client: recvfrom()
Client: close()                   Client: send() ──────────>
                                  Server: recv()
                                  Server: send() ──────────>
                                  Client: recv()
                                  Client: close()
                                  Server: close() (connection socket)

5. マルチスレッドTCPサーバー

基本的なTCPサーバーは一度に1つのクライアントしか処理できない。複数のクライアントを同時に処理するにはマルチスレッドを使用する。

from socket import *
import threading

def handle_client(connectionSocket, addr):
    """Thread function to handle each client"""
    print(f"Thread started: {addr}")
    try:
        message = connectionSocket.recv(1024).decode()
        response = message.upper()
        connectionSocket.send(response.encode())
    finally:
        connectionSocket.close()
        print(f"Thread ended: {addr}")

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(5)
print("Multi-threaded TCP server ready")

while True:
    connectionSocket, addr = serverSocket.accept()
    # Handle client in a new thread
    thread = threading.Thread(
        target=handle_client,
        args=(connectionSocket, addr)
    )
    thread.start()
マルチスレッドサーバーの動作:

  クライアント1 ──接続──> [ウェルカムソケット] ──accept()──> [接続ソケット1] ──Thread 1
  クライアント2 ──接続──> [ウェルカムソケット] ──accept()──> [接続ソケット2] ──Thread 2
  クライアント3 ──接続──> [ウェルカムソケット] ──accept()──> [接続ソケット3] ──Thread 3

  各クライアントが独立したスレッドで処理される

6. まとめ

ソケットプログラミングの核心要約:

  UDPソケット:
  ├── コネクションレス、データグラム単位
  ├── sendto/recvfrom(毎回アドレス指定)
  ├── 高速だが信頼性なし
  └── 用途:DNS、ストリーミング、ゲーム

  TCPソケット:
  ├── コネクション指向、バイトストリーム
  ├── connect/acceptで接続確立
  ├── send/recv(アドレス指定不要)
  ├── 信頼性あるがオーバーヘッドあり
  └── 用途:Web、メール、ファイル転送

7. 確認問題

Q1. UDPソケットでsendto()を使用する理由は?

UDPはコネクションレスプロトコルであるため、接続が事前に確立されない。したがって、各データグラムを送信するたびに宛先IPアドレスとポート番号を明示しなければならない。sendto(data, (ip, port)) の形式で宛先を指定する。一方TCPは connect() ですでに接続が確立されているため send(data) だけで十分である。

Q2. TCPサーバーにおけるウェルカムソケットと接続ソケットの違いは?
  • ウェルカムソケット(serverSocket):クライアントの接続要求を受け入れるソケット。listen()accept() に使用。サーバーが生きている限り維持される。
  • 接続ソケット(connectionSocket)accept() が返す新しいソケット。特定のクライアントとのデータ送受信に使用。通信が終わったら閉じる。

この分離により、サーバーが複数のクライアントと同時に通信できる。

Q3. TCPがバイトストリームであることがプログラミングにどのような影響を与えるか?

TCPはメッセージ境界を維持しない。send() で100バイトを送っても、recv() が一度に100バイトを受け取る保証はない。50バイトずつ2回に分けて受け取る可能性もある。したがって、アプリケーションでメッセージ境界を直接処理しなければならない(例:区切り文字の使用、長さヘッダの追加など)。