TCP vs UDP:主な違いとネットワークスタックでの使い分け

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

背景と理由:異なる問題を解決する2つのプロトコル

アプリケーションが送信するすべてのパケットは、TCPかUDPのどちらかを経由して届く。その選択——そして、それが意図的なものだったかどうか——が、負荷・不安定な接続・パケットロスに対するサービスの振る舞いを決定する。深夜2時にレイテンシのスパイクを追いかけているとき、どのプロトコルが原因かを把握し、実際のトラフィックで確認できれば、どんなダッシュボードよりも速く根本原因にたどり着ける。

TCPとUDPはどちらもOSIモデルのレイヤー4(トランスポート層)に位置し、IPの上で動作する。ただし、その設計思想はまったく逆の方向を向いている。

TCP — 信頼性のために設計された

TCP(Transmission Control Protocol)はデータを流す前に3ウェイハンドシェイクで接続を確立する:

クライアント → サーバー: SYN
サーバー → クライアント: SYN-ACK
クライアント → サーバー: ACK
(接続確立、データ転送開始)

そこからTCPは、パケットが届くこと・順序通りに届くこと・送信側が損失を把握できることを保証する。再送、フロー制御、輻輳制御はすべて組み込み済みだ。その信頼性にはコストが伴う。TCPヘッダーは20〜60バイトあり、新しい接続ごとに最初のバイトが届くまで少なくとも1.5RTTの遅延が発生する。SSH、HTTP/HTTPS、PostgreSQL、MySQL——これらはデータの整合性が必須であるため、そのオーバーヘッドを受け入れている。

UDP — 速度のために設計された

UDP(User Datagram Protocol)はこれらすべてをスキップする。ハンドシェイクなし、確認応答なし、再送なし。パケットをデータグラムに詰め込んで送り出し、次に進む。パケットが失われても、プロトコルは関知しない——それはアプリケーション側が対処する(あるいは無視する)問題だ。

その恩恵は明確で、UDPのヘッダーはわずか8バイト、接続確立のオーバーヘッドもゼロだ。DNS、VoIP、動画ストリーミング、オンラインゲーム、NTP——これらがUDPを使うのは、パケットのロスが200msの再送遅延でユーザー体験が固まってしまうよりもはるかにマシだからだ。

実運用で実際に重要な点を並べると:

  • 接続: TCPはハンドシェイクが必要、UDPはコネクションレス
  • 信頼性: TCPは失われたパケットを再送、UDPはしない
  • 順序: TCPは順序通りに届ける、UDPは順序が前後することがある
  • 速度: UDPはオーバーヘッドが少なくレイテンシが低い
  • 用途: データの整合性にはTCP、リアルタイム性能にはUDP

セットアップ:テストツールキットの準備

実際に試す前に、いくつかのツールが必要だ。Debian/Ubuntuの場合:

sudo apt update
sudo apt install -y netcat-openbsd tcpdump iproute2 python3

RHEL/CentOS/Fedoraの場合:

sudo dnf install -y nmap-ncat tcpdump iproute python3

準備ができているか確認する:

nc --version
tcpdump --version
ss --version

netcatnc)はメインのテストツールで、フラグ一つの違いでシェルから直接TCPまたはUDP接続を開ける。tcpdumpはライブトラフィックをキャプチャして、実際のプロトコルヘッダーをワイヤーレベルで確認できる。ss(ソケット統計)は数年前にnetstatの後継となり、負荷の高いシステムでは明らかに高速だ。

実践:TCPとUDPを実際に使ってみる

NetcatでTCPをテストする

一つのターミナルでポート9000のTCPリスナーを開く:

nc -l -p 9000

別のターミナル(または別のマシン)から接続する:

nc 127.0.0.1 9000

何かを入力すると、リスナー側に表示される。裏では、TCPがまず接続をネゴシエートしている。クライアントをCtrl+Cで終了すると、サーバー側にEOFが届く。これがTCPのグレースフルクローズ(FIN/FIN-ACKシーケンス)だ。

NetcatでUDPをテストする

UDPには-uフラグを追加する:

# リスナー
nc -u -l -p 9001

# 送信側(別のターミナル)
echo "hello via udp" | nc -u 127.0.0.1 9001

接続の確立はない。パケットは届くか届かないかのどちらかだ。リスナーを終了してクライアントから送り続けても、エラーは出ない。UDPは相手がいなくなったことを知る手段を持っていないからだ。

ソケットプログラミング:PythonでのTCP vs UDP

APIレベルでの違いは文字通り定数一つだ。最小限のTCPサーバー:

import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:  # SOCK_STREAM = TCP
    s.bind(('0.0.0.0', 9000))
    s.listen(1)
    conn, addr = s.accept()  # クライアントが接続するまでブロック
    with conn:
        data = conn.recv(1024)
        print(f"受信データ: {data.decode()}")
        conn.sendall(b"ACK")

UDPの場合——accept()なし、接続状態なし:

import socket

with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:  # SOCK_DGRAM = UDP
    s.bind(('0.0.0.0', 9001))
    data, addr = s.recvfrom(1024)  # ハンドシェイクなし、データグラムを待つだけ
    print(f"{addr} から受信: {data.decode()}")
    s.sendto(b"got it", addr)

SOCK_STREAM vs SOCK_DGRAM——APIレベルでの判断はこれだけだ。信頼性・順序保証・再送といったすべての挙動は、このフラグ一つから自動的に決まる。

アプリケーションに適したプロトコルを選ぶ

シンプルなルール:パケットが一つ失われるだけでユーザー体験が壊れたりデータが破損したりするならTCPを使う。パケットのロスが軽微な乱れに過ぎない——あるいはコンマ数秒後の次の更新で上書きされる——ならUDPが有利だ。

具体的なケース:

  • REST API / データベースクエリ: TCP——完全なレスポンスを順序通りに受け取る必要がある
  • DNS ルックアップ: UDP——一般的なクエリは512バイト以下で50ms未満で解決する。4096バイトを超えるレスポンス(EDNS)ではTCPにフォールバックする
  • ビデオ通話: UDP——30fpsではフレームのドロップはほぼ気づかないが、TCPの500ms再送遅延が発生すると通話が完全に固まる
  • ファイル転送(SCP、rsync): TCP——すべてのバイトが完全に届く必要がある
  • ゲームの状態同期: UDP——位置情報の更新は次のパケットで上書きされるため問題ない
  • ログ転送(syslog): UDPが一般的。ログが稀に欠けても通常は許容範囲内

確認と監視:TCPとUDPの動作をリアルタイムで見る

ssでアクティブな接続を確認する

システム上のすべてのTCP接続を確認する:

ss -tnp

UDPソケット(注:UDPにはESTABLISHEDの概念がなく、未接続ソケットはUNCONNと表示される):

ss -unp

特定のポートでフィルタリングする:

ss -tnp sport = :443
ss -unp dport = :53

tcpdumpでトラフィックをキャプチャする

ポート9000のTCPトラフィックをキャプチャする:

sudo tcpdump -i lo tcp port 9000 -v

データの前に3ウェイハンドシェイク——SYN、SYN-ACK、ACK——がはっきり見える。UDPの場合:

sudo tcpdump -i lo udp port 9001 -v

最初のパケットがそのままデータだ。セットアップもネゴシエーションもない。これがワイヤーレベルで目に見える、本質的な違いだ。

netstatでTCPの状態を確認する(レガシー)

ssが使えない場合:

netstat -tnp | grep ESTABLISHED
netstat -unp

TCPの再送を監視する

本番環境で再送レートが高い場合、通常はネットワーク輻輳かパケットロスがどこかで発生している。以下で確認できる:

ss -s
# または
cat /proc/net/snmp | grep -i retrans

レイテンシに敏感なサービスで再送が増加していたら、追跡する価値がある。輻輳の発生源を特定するか、あるいはそのトラフィックパターンが本当にTCPを必要としているのかを再考するかだ。

TCPとUDPは1983年の4.2BSDからBSDソケットAPIの一部として存在する——40年以上が経った今もなお、あらゆるネットワークスタックの基盤だ。迷わず適切なプロトコルを選び、推測ではなくtcpdumpで仮定を検証できるエンジニアは、本番障害のデバッグを一貫して速く解決し、信頼性とレイテンシがトレードオフになる局面でも的確な判断を下せる。

Share: