メインコンテンツまでスキップ

TCP基礎

所要時間

約40分

学べること

  • TCP/IPプロトコルスタックの概要
  • TCPの特徴と役割(信頼性、順序保証、フロー制御)
  • 3ウェイハンドシェイクと4ウェイハンドシェイク
  • TCPの状態遷移(ESTABLISHED、TIME_WAIT等)
  • ポート番号とソケット
  • TCPとUDPの違いと使い分け
  • HTTP、JDBC、Redis等がTCPをどう使っているか
  • コネクションプールの仕組みと重要性
  • 実運用でのTCPチューニング

前提知識

  • ネットワークの基本概念(IPアドレス、パケット)
  • OSI参照モデルまたはTCP/IPモデルの概要

1. TCP/IPプロトコルスタック

1.1 レイヤー構造

┌─────────────────────────────────────────────────────────────┐
│ TCP/IPプロトコルスタック │
├─────────────────────────────────────────────────────────────┤
│ │
│ OSI参照モデル TCP/IPモデル プロトコル例 │
│ ───────────────────────────────────────────────────────── │
│ │
│ 7. アプリケーション層 ┐ │
│ 6. プレゼンテーション層├─ アプリケーション層 HTTP, HTTPS, │
│ 5. セッション層 ┘ FTP, SSH, DNS │
│ │
│ 4. トランスポート層 ─── トランスポート層 TCP, UDP │
│ ↑ 今回の主役 │
│ │
│ 3. ネットワーク層 ─── インターネット層 IP, ICMP │
│ │
│ 2. データリンク層 ┐ │
│ 1. 物理層 ┘── ネットワーク Ethernet, │
│ インターフェース層 Wi-Fi │
│ │
└─────────────────────────────────────────────────────────────┘

1.2 データのカプセル化

┌─────────────────────────────────────────────────────────────┐
│ データのカプセル化 │
├─────────────────────────────────────────────────────────────┤
│ │
│ アプリケーション層 │
│ ┌───────────────────────────────────────────┐ │
│ │ データ (Payload) │ │
│ └───────────────────────────────────────────┘ │
│ ↓ │
│ トランスポート層 (TCP) │
│ ┌──────────┬───────────────────────────────┐ │
│ │ TCPヘッダ │ データ │ = セグメント │
│ └──────────┴───────────────────────────────┘ │
│ ↓ │
│ インターネット層 (IP) │
│ ┌─────────┬──────────┬─────────────────────┐ │
│ │ IPヘッダ │ TCPヘッダ │ データ │ = パケット │
│ └─────────┴──────────┴─────────────────────┘ │
│ ↓ │
│ ネットワークインターフェース層 (Ethernet) │
│ ┌───────────┬─────────┬──────────┬─────────┬─────┐ │
│ │Etherヘッダ│ IPヘッダ │ TCPヘッダ │ データ │ FCS │= フレーム│
│ └───────────┴─────────┴──────────┴─────────┴─────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

2. TCPの特徴

2.1 TCPが提供する機能

┌─────────────────────────────────────────────────────────────┐
│ TCPの主要機能 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 信頼性のある通信 (Reliable Delivery) │
│ - パケットロスの検出と再送 │
│ - チェックサムによるデータ整合性検証 │
│ - ACK(確認応答)による到達確認 │
│ │
│ 2. 順序保証 (Ordered Delivery) │
│ - シーケンス番号による順序管理 │
│ - 到着順序が異なっても正しい順序で再構築 │
│ │
│ 3. コネクション指向 (Connection-Oriented) │
│ - 通信前に接続確立(3ウェイハンドシェイク) │
│ - 通信後に接続終了(4ウェイハンドシェイク) │
│ │
│ 4. フロー制御 (Flow Control) │
│ - 受信側のバッファ溢れを防止 │
│ - ウィンドウサイズによる送信量調整 │
│ │
│ 5. 輻輳制御 (Congestion Control) │
│ - ネットワーク混雑時の送信レート調整 │
│ - スロースタート、輻輳回避アルゴリズム │
│ │
│ 6. 全二重通信 (Full Duplex) │
│ - 双方向同時通信が可能 │
│ │
└─────────────────────────────────────────────────────────────┘

2.2 TCPヘッダの構造

┌─────────────────────────────────────────────────────────────┐
│ TCPヘッダ(20〜60バイト) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 0 1 2 3
│ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
│ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
│ | 送信元ポート | 宛先ポート |
│ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
│ | シーケンス番号 |
│ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
│ | 確認応答番号 (ACK番号) |
│ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
│ | データ | |U|A|P|R|S|F| |
│ | オフセット| 予約 |R|C|S|S|Y|I| ウィンドウサイズ |
│ | | |G|K|H|T|N|N| |
│ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
│ | チェックサム | 緊急ポインタ |
│ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
│ | オプション(可変長) |
│ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
│ │
│ 主要フラグ: │
│ - SYN: 接続開始 │
│ - ACK: 確認応答 │
│ - FIN: 接続終了 │
│ - RST: 接続リセット │
│ - PSH: プッシュ(バッファリングせず即座に転送) │
│ - URG: 緊急データ │
│ │
└─────────────────────────────────────────────────────────────┘

3. TCP接続の確立と終了

3.1 3ウェイハンドシェイク(接続確立)

┌─────────────────────────────────────────────────────────────┐
│ 3ウェイハンドシェイク │
├─────────────────────────────────────────────────────────────┤
│ │
│ クライアント サーバー │
│ │ │ │
│ │ │ [LISTEN] │
│ │ │ │
│ Step 1 │ ──────── SYN (seq=x) ─────────► │ │
│ │ │ │
│ [SYN_SENT] │ [SYN_RECEIVED] │
│ │ │ │
│ Step 2 │ ◄─── SYN+ACK (seq=y, ack=x+1) ── │ │
│ │ │ │
│ │ │ │
│ Step 3 │ ──────── ACK (ack=y+1) ────────► │ │
│ │ │ │
│ [ESTABLISHED] │ [ESTABLISHED] │
│ │ │ │
│ │ 双方向通信可能 │ │
│ │ ◄────────────────────────────► │ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 各ステップの意味: │
│ │
│ 1. SYN: クライアント→サーバー │
│ 「接続したい。私のシーケンス番号はxから始める」 │
│ │
│ 2. SYN+ACK: サーバー→クライアント │
│ 「OK。私のシーケンス番号はyから始める。x+1を待っている」 │
│ │
│ 3. ACK: クライアント→サーバー │
│ 「了解。y+1を待っている」 │
│ │
└─────────────────────────────────────────────────────────────┘

3.2 4ウェイハンドシェイク(接続終了)

┌─────────────────────────────────────────────────────────────┐
│ 4ウェイハンドシェイク │
├─────────────────────────────────────────────────────────────┤
│ │
│ クライアント サーバー │
│ │ │ │
│ [ESTABLISHED] │ [ESTABLISHED] │
│ │ │ │
│ Step 1 │ ──────── FIN (seq=x) ─────────► │ │
│ │ │ │
│ [FIN_WAIT_1] │ [CLOSE_WAIT] │
│ │ │ │
│ Step 2 │ ◄─────── ACK (ack=x+1) ───────── │ │
│ │ │ │
│ [FIN_WAIT_2] │ │
│ │ │ │
│ │ (サーバーが残りのデータを送信)│ │
│ │ │ │
│ Step 3 │ ◄─────── FIN (seq=y) ────────── │ │
│ │ │ │
│ [TIME_WAIT] │ [LAST_ACK] │
│ │ │ │
│ Step 4 │ ──────── ACK (ack=y+1) ────────► │ │
│ │ │ │
│ │ │ [CLOSED] │
│ │ │ │
│ (2MSL待機) │ │
│ │ │ │
│ [CLOSED]│ │ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ なぜ4ステップ必要なのか: │
│ - TCP は全二重通信のため、各方向を独立して終了する必要 │
│ - クライアント→サーバー方向の終了 (Step 1-2) │
│ - サーバー→クライアント方向の終了 (Step 3-4) │
│ │
└─────────────────────────────────────────────────────────────┘

3.3 TCP状態遷移

┌─────────────────────────────────────────────────────────────┐
│ 主要なTCP状態 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【接続前】 │
│ CLOSED - 初期状態 │
│ LISTEN - サーバーが接続待ち │
│ │
│ 【接続確立中】 │
│ SYN_SENT - クライアントがSYN送信後 │
│ SYN_RECEIVED - サーバーがSYN受信後 │
│ │
│ 【接続確立後】 │
│ ESTABLISHED - 接続確立済み、データ送受信可能 │
│ │
│ 【接続終了中(アクティブクローズ側)】 │
│ FIN_WAIT_1 - FIN送信後、ACK待ち │
│ FIN_WAIT_2 - ACK受信後、相手のFIN待ち │
│ TIME_WAIT - 最後のACK送信後、2MSL待機 │
│ │
│ 【接続終了中(パッシブクローズ側)】 │
│ CLOSE_WAIT - FIN受信後、アプリ終了待ち │
│ LAST_ACK - FIN送信後、最後のACK待ち │
│ │
│ 【同時クローズ】 │
│ CLOSING - 両側が同時にFINを送信した場合 │
│ │
└─────────────────────────────────────────────────────────────┘

3.4 TIME_WAITの重要性

┌─────────────────────────────────────────────────────────────┐
│ TIME_WAIT状態の役割 │
├─────────────────────────────────────────────────────────────┤
│ │
│ TIME_WAIT: 接続を閉じた後、2×MSL(Maximum Segment Lifetime)│
│ の間、その接続の情報を保持する状態 │
│ │
│ MSL: 通常30秒〜2分(Linux デフォルト: 60秒) │
│ → TIME_WAIT は 2×60秒 = 120秒 = 2分 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【なぜTIME_WAITが必要か】 │
│ │
│ 1. 遅延パケットの処理 │
│ - ネットワーク上に残っている古いパケットが │
│ 新しい接続に誤って届くことを防ぐ │
│ │
│ 2. 最後のACKの再送 │
│ - 最後のACKが失われた場合、相手がFINを再送する │
│ - TIME_WAIT中なら再度ACKを返せる │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【TIME_WAITが問題になるケース】 │
│ │
│ 高トラフィックサーバーで大量のTIME_WAIT接続が滞留 │
│ → ポート枯渇、メモリ消費 │
│ │
│ 確認コマンド: │
│ $ netstat -an | grep TIME_WAIT | wc -l │
│ $ ss -s | grep timewait │
│ │
└─────────────────────────────────────────────────────────────┘

4. ポート番号とソケット

4.1 ポート番号の概念

┌─────────────────────────────────────────────────────────────┐
│ ポート番号 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ポート番号: 0 〜 65535 (16ビット) │
│ │
│ 【ポート番号の分類】 │
│ │
│ ┌──────────────┬──────────────┬─────────────────────────┐ │
│ │ 範囲 │ 名称 │ 用途 │ │
│ ├──────────────┼──────────────┼─────────────────────────┤ │
│ │ 0 - 1023 │ Well-known │ 標準的なサービス │ │
│ │ │ ポート │ (HTTP:80, HTTPS:443等) │ │
│ │ │ │ root権限が必要(Unix) │ │
│ ├──────────────┼──────────────┼─────────────────────────┤ │
│ │ 1024 - 49151 │ Registered │ IANA登録済みサービス │ │
│ │ │ ポート │ (MySQL:3306等) │ │
│ ├──────────────┼──────────────┼─────────────────────────┤ │
│ │ 49152 - 65535│ Dynamic/ │ クライアント側の │ │
│ │ │ Ephemeral │ 動的ポート │ │
│ │ │ ポート │ (OSが自動割当) │ │
│ └──────────────┴──────────────┴─────────────────────────┘ │
│ │
│ 【主要なWell-knownポート】 │
│ │
│ 20/21 FTP (データ/制御) │
│ 22 SSH │
│ 23 Telnet │
│ 25 SMTP │
│ 53 DNS (TCP/UDP) │
│ 80 HTTP │
│ 110 POP3 │
│ 143 IMAP │
│ 443 HTTPS │
│ 3306 MySQL │
│ 5432 PostgreSQL │
│ 6379 Redis │
│ 8080 HTTP代替(アプリサーバー) │
│ │
└─────────────────────────────────────────────────────────────┘

4.2 ソケット

┌─────────────────────────────────────────────────────────────┐
│ ソケットとコネクション │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【ソケットとは】 │
│ - ネットワーク通信のエンドポイント │
│ - IPアドレス + ポート番号 の組み合わせ │
│ │
│ 例: 192.168.1.10:8080 (ソケット) │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【TCP接続の一意性】 │
│ │
│ TCP接続は以下の5つ組(5-tuple)で一意に識別される: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ (送信元IP, 送信元ポート, 宛先IP, 宛先ポート, TCP) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 例: │
│ (192.168.1.10, 52340, 93.184.216.34, 443, TCP) │
│ (192.168.1.10, 52341, 93.184.216.34, 443, TCP) │
│ ↑ 送信元ポートが異なるので別の接続 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【サーバー側のソケット】 │
│ │
│ サーバー (0.0.0.0:8080 LISTEN) │
│ │ │
│ ├── 接続1: (Client1_IP:52340, Server_IP:8080) │
│ ├── 接続2: (Client1_IP:52341, Server_IP:8080) │
│ ├── 接続3: (Client2_IP:49200, Server_IP:8080) │
│ └── ... │
│ │
│ → 1つのLISTENソケットで複数のクライアント接続を処理 │
│ │
└─────────────────────────────────────────────────────────────┘

5. TCPとUDPの比較

5.1 特徴の比較

┌─────────────────────────────────────────────────────────────┐
│ TCP vs UDP │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────┬────────────────┬────────────────┐ │
│ │ 特徴 │ TCP │ UDP │ │
│ ├────────────────────┼────────────────┼────────────────┤ │
│ │ 接続 │ コネクション型 │ コネクションレス │ │
│ │ 信頼性 │ あり(再送) │ なし │ │
│ │ 順序保証 │ あり │ なし │ │
│ │ フロー/輻輳制御 │ あり │ なし │ │
│ │ ヘッダサイズ │ 20-60バイト │ 8バイト │ │
│ │ オーバーヘッド │ 大きい │ 小さい │ │
│ │ 速度 │ 相対的に遅い │ 相対的に速い │ │
│ │ 通信形態 │ 1対1 │ 1対1, 1対多 │ │
│ │ ブロードキャスト │ 不可 │ 可能 │ │
│ └────────────────────┴────────────────┴────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

5.2 使い分けのガイドライン

┌─────────────────────────────────────────────────────────────┐
│ TCP/UDP 使い分けガイド │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【TCP を使うべきケース】 │
│ │
│ - HTTP/HTTPS(Web通信) │
│ - SMTP/IMAP/POP3(メール) │
│ - FTP(ファイル転送) │
│ - SSH(リモート接続) │
│ - データベース接続 │
│ - データの完全性が重要な場合 │
│ │
│ → 「全てのデータが確実に届く必要がある」場合 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【UDP を使うべきケース】 │
│ │
│ - DNS(名前解決)※TCP も使用 │
│ - NTP(時刻同期) │
│ - DHCP(IPアドレス取得) │
│ - VoIP/音声通話 │
│ - ビデオストリーミング │
│ - オンラインゲーム │
│ - IoTセンサーデータ │
│ │
│ → 「多少のロスより速度・リアルタイム性が重要」な場合 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【HTTPに関する補足】 │
│ │
│ HTTP/1.1, HTTP/2: TCP上で動作 │
│ HTTP/3 (QUIC): UDP上で動作(TCPの機能をUDP上で実装) │
│ │
└─────────────────────────────────────────────────────────────┘

6. アプリケーションプロトコルとTCP

6.1 TCPが提供するもの vs アプリが実装するもの

┌─────────────────────────────────────────────────────────────┐
│ TCPとアプリケーションの責務分担 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【TCPが提供するもの(全プロトコル共通)】 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ✓ 接続の確立と終了 │ │
│ │ - 3ウェイハンドシェイク / 4ウェイハンドシェイク │ │
│ │ - 相手が存在することの確認 │ │
│ │ │ │
│ │ ✓ 信頼性のあるバイトストリーム配送 │ │
│ │ - パケットロス時の再送 │ │
│ │ - 順序の保証(届いた順に並べ替え) │ │
│ │ - 重複排除 │ │
│ │ │ │
│ │ ✓ フロー制御・輻輳制御 │ │
│ │ - 受信側のバッファ溢れ防止 │ │
│ │ - ネットワーク混雑時の送信抑制 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ TCPは「バイト列を確実に届ける」だけ。 │
│ そのバイト列の意味はTCPにはわからない。 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【アプリケーションが実装するもの(プロトコルごとに異なる)】│
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ✗ メッセージの区切り(フレーミング) │ │
│ │ - どこからどこまでが1つのメッセージか │ │
│ │ - TCPはバイトストリームなので区切りがない │ │
│ │ │ │
│ │ ✗ メッセージの意味(セマンティクス) │ │
│ │ - リクエスト/レスポンスのパターン │ │
│ │ - コマンドの種類、エラーの定義 │ │
│ │ │ │
│ │ ✗ 認証・セッション管理 │ │
│ │ - ユーザー認証の方式 │ │
│ │ - セッション/トランザクションの概念 │ │
│ │ │ │
│ │ ✗ 接続の使い方(接続戦略) │ │
│ │ - 1リクエスト1接続 or 接続を使い回す │ │
│ │ - コネクションプールの有無 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

6.2 プロトコルごとの違い(比較表)

HTTP/1.1PostgreSQL (JDBC)RedisWebSocket
ポート80/4435432637980/443
メッセージ形式テキスト(ヘッダー+ボディ)バイナリ(独自形式)テキスト(RESP)バイナリフレーム
区切り方Content-Length or chunkedメッセージ長を先頭に付与\r\n 区切りフレームヘッダに長さを含む
通信パターンリクエスト→レスポンスリクエスト→レスポンスコマンド→レスポンス双方向(いつでも送受信)
状態ステートレス(セッションはアプリ層)ステートフル(トランザクション)ほぼステートレスステートフル(接続=セッション)
認証ヘッダー(Authorization等)接続時に認証ハンドシェイクAUTHコマンドHTTPヘッダー or メッセージ内
接続戦略Keep-Aliveで再利用接続プール必須接続プール推奨長時間維持

6.3 具体例:ワイヤ上で何が違うか

┌─────────────────────────────────────────────────────────────┐
│ 実際にTCP上を流れるデータの違い │
├─────────────────────────────────────────────────────────────┤
│ │
│ すべてのプロトコルで共通: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ TCP接続確立 (3-way handshake) │ │
│ │ SYN → SYN+ACK → ACK │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ↓ ここから先がプロトコルごとに違う ↓ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【HTTP/1.1】テキストベース、\r\n区切り │
│ │
│ → クライアントが送信: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GET /users HTTP/1.1\r\n │ │
│ │ Host: api.example.com\r\n │ │
│ │ Accept: application/json\r\n │ │
│ │ \r\n ← 空行でヘッダー終了 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ← サーバーが応答: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ HTTP/1.1 200 OK\r\n │ │
│ │ Content-Type: application/json\r\n │ │
│ │ Content-Length: 27\r\n ← ボディの長さを指定 │ │
│ │ \r\n │ │
│ │ {"users":[{"id":1}]} ← ボディ(27バイト) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【PostgreSQL】バイナリ、長さプレフィックス │
│ │
│ → 接続時の認証ハンドシェイク: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ [00 00 00 08] ← メッセージ長(8バイト) │ │
│ │ [00 03 00 00] ← プロトコルバージョン(3.0) │ │
│ │ user\0postgres\0 ← パラメータ(ユーザー名) │ │
│ │ database\0mydb\0 ← パラメータ(DB名) │ │
│ │ \0 ← パラメータ終端 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ → クエリ実行: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ [Q] ← メッセージタイプ(Query) │ │
│ │ [00 00 00 1A] ← メッセージ長 │ │
│ │ SELECT * FROM users\0 ← クエリ文字列(null終端) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【Redis】テキストベース(RESP)、\r\n区切り │
│ │
│ → コマンド送信: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ *2\r\n ← 配列、2要素 │ │
│ │ $3\r\n ← バルク文字列、3バイト │ │
│ │ GET\r\n ← "GET" │ │
│ │ $4\r\n ← バルク文字列、4バイト │ │
│ │ user\r\n ← "user" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ← サーバー応答: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ $5\r\n ← バルク文字列、5バイト │ │
│ │ alice\r\n ← "alice" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【WebSocket】バイナリフレーム │
│ │
│ → 最初はHTTPでアップグレード要求: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GET /chat HTTP/1.1\r\n │ │
│ │ Upgrade: websocket\r\n │ │
│ │ Connection: Upgrade\r\n │ │
│ │ Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ← サーバーが101で応答後、プロトコルが切り替わる: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ [81] ← FIN=1, opcode=1(text) │ │
│ │ [85] ← MASK=1, length=5 │ │
│ │ [xx xx xx xx] ← マスクキー(4バイト) │ │
│ │ [masked data] ← XORマスクされた"hello" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

6.4 なぜこの違いが重要か

┌─────────────────────────────────────────────────────────────┐
│ 違いを理解することで見えるもの │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. トラブルシューティング │
│ ───────────────────── │
│ tcpdump/Wiresharkでパケットを見たとき: │
│ - TCPレベルの問題か?(再送、RST等) │
│ - アプリレベルの問題か?(不正なフォーマット等) │
│ を切り分けられる │
│ │
│ 2. パフォーマンスチューニング │
│ ───────────────────────── │
│ - TCPレベル: バッファサイズ、Keep-Alive設定 │
│ - アプリレベル: コネクションプール、パイプライン │
│ どちらをチューニングすべきかわかる │
│ │
│ 3. プロトコル設計の理解 │
│ ───────────────────── │
│ なぜHTTP/2は1接続で多重化できるのか? │
│ → TCPは順序保証するだけで、HTTP/2がストリーム管理 │
│ │
│ なぜWebSocketはHTTPでハンドシェイクするのか? │
│ → ファイアウォール/プロキシを通過するため │
│ │
│ 4. セキュリティの理解 │
│ ───────────────────── │
│ TCPレベルで暗号化(TLS)すると: │
│ - アプリプロトコルの内容は見えなくなる │
│ - でもTCPヘッダー(ポート、IP)は見える │
│ - メタデータの漏洩リスクを理解できる │
│ │
└─────────────────────────────────────────────────────────────┘

6.5 例:HTTPとTCPの関係

┌─────────────────────────────────────────────────────────────┐
│ HTTPはTCPの上で動く │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【HTTP/1.0 - リクエストごとに接続】 │
│ │
│ クライアント サーバー │
│ │ │ │
│ │── TCP 3way handshake ──│ 接続確立 │
│ │ │ │
│ │── GET /index.html ────►│ リクエスト │
│ │◄── 200 OK + HTML ──────│ レスポンス │
│ │ │ │
│ │── TCP 4way handshake ──│ 接続終了 │
│ │ │ │
│ │── TCP 3way handshake ──│ また接続確立(遅い!) │
│ │── GET /style.css ─────►│ │
│ │◄── 200 OK + CSS ───────│ │
│ │── TCP 4way handshake ──│ また接続終了 │
│ │
│ 問題: 毎回ハンドシェイクのオーバーヘッドが発生 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【HTTP/1.1 - Keep-Alive(持続的接続)】 │
│ │
│ クライアント サーバー │
│ │ │ │
│ │── TCP 3way handshake ──│ 接続確立(1回だけ) │
│ │ │ │
│ │── GET /index.html ────►│ │
│ │◄── 200 OK + HTML ──────│ │
│ │ │ ← 接続を維持 │
│ │── GET /style.css ─────►│ │
│ │◄── 200 OK + CSS ───────│ │
│ │ │ ← まだ維持 │
│ │── GET /script.js ─────►│ │
│ │◄── 200 OK + JS ────────│ │
│ │ │ │
│ │ (タイムアウト後) │ │
│ │── TCP 4way handshake ──│ 接続終了 │
│ │
│ 改善: 1つのTCP接続で複数のリクエストを処理 │
│ ただしリクエストは順番に処理(Head-of-Line Blocking)│
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【HTTP/2 - 多重化】 │
│ │
│ クライアント サーバー │
│ │ │ │
│ │── TCP 3way handshake ──│ 接続確立(1回だけ) │
│ │ │ │
│ │══ Stream 1: GET /a ═══►│ │
│ │══ Stream 2: GET /b ═══►│ 同時に複数リクエスト │
│ │══ Stream 3: GET /c ═══►│ │
│ │ │ │
│ │◄══ Stream 2: /b ═══════│ 順不同でレスポンス │
│ │◄══ Stream 1: /a ═══════│ │
│ │◄══ Stream 3: /c ═══════│ │
│ │
│ 改善: 1つのTCP接続で複数のリクエストを並列処理 │
│ │
└─────────────────────────────────────────────────────────────┘

6.6 例:データベース接続(JDBC)とTCP

┌─────────────────────────────────────────────────────────────┐
│ JDBCはTCPコネクションを使う │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【毎回接続する場合(非効率)】 │
│ │
│ アプリ PostgreSQL (5432) │
│ │ │ │
│ │── TCP 3way handshake ────►│ 接続確立 │
│ │── 認証(ユーザー/パス)──►│ 認証処理 │
│ │◄── 認証OK ────────────────│ │
│ │ │ │
│ │── SELECT * FROM users ───►│ クエリ実行 │
│ │◄── 結果セット ────────────│ │
│ │ │ │
│ │── TCP 4way handshake ────►│ 接続終了 │
│ │ │ │
│ │ 次のリクエストでまた最初から... │
│ │
│ 問題: │
│ - 毎回ハンドシェイク(1.5 RTT) │
│ - 毎回認証処理 │
│ - DBサーバー側でのプロセス/スレッド生成コスト │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【コネクションプール(効率的)】 │
│ │
│ アプリ HikariCP等 PostgreSQL │
│ │ (プール) │ │
│ │ │ │ │
│ │ │── TCP接続 ──────►│ 起動時に接続確立 │
│ │ │── TCP接続 ──────►│ 複数コネクション │
│ │ │── TCP接続 ──────►│ をプールしておく │
│ │ │ │ │
│ │ │ 【接続済みプール】 │
│ │ │ ┌─────────────┐ │ │
│ │ │ │ Conn1: IDLE │ │ │
│ │ │ │ Conn2: IDLE │ │ │
│ │ │ │ Conn3: IDLE │ │ │
│ │ │ └─────────────┘ │ │
│ │ │ │ │
│ │─ getConn() ─►│ │ │
│ │◄─ Conn1 ─────│ │ │
│ │ │ │ │
│ │── SELECT ────────────────────────► クエリ実行 │
│ │◄── 結果 ─────────────────────────│(接続確立不要) │
│ │ │ │ │
│ │─ close() ───►│ │ │
│ │ │ Conn1をプールに戻す │
│ │ │ │ │
│ │
│ 利点: │
│ - 接続確立のオーバーヘッドなし │
│ - 認証処理も不要(接続済み) │
│ - DBサーバーの負荷軽減 │
│ │
└─────────────────────────────────────────────────────────────┘

6.7 例:その他のプロトコルとTCP

┌─────────────────────────────────────────────────────────────┐
│ 様々なプロトコルのTCP利用パターン │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【Redis】 │
│ │
│ - ポート: 6379 │
│ - 持続的接続(コネクションプール推奨) │
│ - パイプライニング: 複数コマンドを一括送信 │
│ │
│ GET key1 │
│ GET key2 → 1つのTCPパケットで送信 │
│ GET key3 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【WebSocket】 │
│ │
│ - HTTPでハンドシェイク後、TCPコネクションを維持 │
│ - 双方向リアルタイム通信 │
│ │
│ クライアント サーバー │
│ │ │ │
│ │── HTTP Upgrade要求 ───►│ │
│ │◄── 101 Switching ──────│ │
│ │ │ │
│ │◄═══ WebSocket通信 ════►│ TCP接続を維持したまま │
│ │◄═══ 双方向に送受信 ═══►│ HTTPではなくなる │
│ │ │ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【gRPC】 │
│ │
│ - HTTP/2上で動作 │
│ - つまりTCP上でHTTP/2、その上でgRPC │
│ - ストリーミング対応 │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ gRPC │ │
│ ├─────────────────────────────────────────┤ │
│ │ HTTP/2 │ │
│ ├─────────────────────────────────────────┤ │
│ │ TCP │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【SSH】 │
│ │
│ - ポート: 22 │
│ - 1つのTCP接続で複数チャンネルを多重化 │
│ - ポートフォワーディング(TCP接続のトンネリング) │
│ │
│ ssh -L 3306:db.internal:3306 bastion │
│ ↓ │
│ localhost:3306 → SSHトンネル → db.internal:3306 │
│ (ローカルのMySQL接続がSSH経由でリモートDBへ) │
│ │
└─────────────────────────────────────────────────────────────┘

6.8 コネクションプールの重要性

┌─────────────────────────────────────────────────────────────┐
│ TCPコネクション確立のコスト │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【1回の接続確立にかかる時間】 │
│ │
│ 例: クライアント ←→ DB サーバー間の RTT = 1ms │
│ │
│ 1. TCP 3way handshake: 1.5 RTT = 1.5ms │
│ 2. DB認証処理: 約 0.5ms │
│ 3. 実際のクエリ: 約 0.1ms │
│ ────────────────────────────────────── │
│ 合計: 約 2.1ms │
│ │
│ → クエリ0.1msのために2msのオーバーヘッド │
│ → 95%が接続確立のコスト │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【コネクションプールを使った場合】 │
│ │
│ 1. プールから取得: 約 0.01ms(メモリ操作のみ) │
│ 2. 実際のクエリ: 約 0.1ms │
│ 3. プールに返却: 約 0.01ms │
│ ────────────────────────────────────── │
│ 合計: 約 0.12ms │
│ │
│ → 約17倍高速 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【プール設定のポイント】 │
│ │
│ HikariCP (Java) の例: │
│ │
│ maximumPoolSize: 10 # 最大接続数 │
│ minimumIdle: 5 # 最小アイドル接続数 │
│ connectionTimeout: 30000 # 接続取得タイムアウト(ms) │
│ idleTimeout: 600000 # アイドル接続のタイムアウト(ms) │
│ maxLifetime: 1800000 # 接続の最大生存時間(ms) │
│ │
│ 注意: │
│ - DBサーバーのmax_connections を超えないように設定 │
│ - アプリサーバーの台数 × プールサイズ ≤ DB max_connections │
│ │
└─────────────────────────────────────────────────────────────┘

7. TCPの確認コマンド

7.1 接続状態の確認

# netstat(古いがまだ使われている)
netstat -an # 全接続を数値表示
netstat -ant # TCPのみ
netstat -antp # TCPとプロセス名(Linux、要root)
netstat -s # プロトコル統計

# ss(netstatの後継、推奨)
ss -s # 統計サマリー
ss -ant # TCP接続一覧
ss -antp # TCP接続とプロセス
ss state established # ESTABLISHED状態のみ
ss state time-wait # TIME_WAIT状態のみ
ss -o state established # タイマー情報付き

# 出力例:
# State Recv-Q Send-Q Local Address:Port Peer Address:Port
# ESTAB 0 0 192.168.1.10:22 192.168.1.100:52340
# TIME-WAIT 0 0 192.168.1.10:80 192.168.1.100:52341

7.2 リスニングポートの確認

# リッスン中のポートを確認
ss -tlnp # TCPリッスンポート
ss -ulnp # UDPリッスンポート

# 特定ポートの確認
ss -tlnp | grep 8080
lsof -i :8080 # macOS/Linux

# ポートを使用しているプロセスを特定
lsof -i TCP:8080
fuser 8080/tcp # Linux

7.3 接続テスト

# telnet(TCPポート接続テスト)
telnet example.com 80

# nc/netcat(より柔軟)
nc -vz example.com 80 # TCP接続テスト
nc -vzu example.com 53 # UDP接続テスト

# 接続タイムアウト付き
nc -w 5 -vz example.com 443

# curl(HTTP/HTTPS)
curl -v https://example.com
curl --connect-timeout 5 https://example.com

8. TCPチューニング

8.1 よく調整されるパラメータ

┌─────────────────────────────────────────────────────────────┐
│ 主要なTCPカーネルパラメータ(Linux) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【接続関連】 │
│ │
│ net.core.somaxconn = 4096 │
│ - LISTENソケットのバックログキューサイズ │
│ - 接続要求の待ち行列の最大長 │
│ │
│ net.ipv4.tcp_max_syn_backlog = 4096 │
│ - SYN_RECEIVED状態の接続の最大数 │
│ - SYN flood攻撃対策にも関連 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【TIME_WAIT関連】 │
│ │
│ net.ipv4.tcp_tw_reuse = 1 │
│ - TIME_WAIT状態のソケットを新しい接続で再利用 │
│ - クライアント側で有効(アウトバウンド接続) │
│ │
│ net.ipv4.tcp_fin_timeout = 30 │
│ - FIN_WAIT_2状態のタイムアウト(秒) │
│ - デフォルト60秒を短縮 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【バッファ関連】 │
│ │
│ net.core.rmem_max = 16777216 │
│ - 受信バッファの最大サイズ │
│ │
│ net.core.wmem_max = 16777216 │
│ - 送信バッファの最大サイズ │
│ │
│ net.ipv4.tcp_rmem = "4096 87380 16777216" │
│ - TCP受信バッファ(最小/デフォルト/最大) │
│ │
│ net.ipv4.tcp_wmem = "4096 65536 16777216" │
│ - TCP送信バッファ(最小/デフォルト/最大) │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【Keep-Alive関連】 │
│ │
│ net.ipv4.tcp_keepalive_time = 600 │
│ - アイドル後、最初のKeep-Aliveを送るまでの時間(秒) │
│ │
│ net.ipv4.tcp_keepalive_intvl = 60 │
│ - Keep-Aliveプローブの間隔(秒) │
│ │
│ net.ipv4.tcp_keepalive_probes = 5 │
│ - 応答なしで接続を切断するまでのプローブ回数 │
│ │
└─────────────────────────────────────────────────────────────┘

8.2 設定の確認と変更

# 現在の設定確認
sysctl net.ipv4.tcp_tw_reuse
sysctl net.core.somaxconn

# 一時的な変更(再起動で元に戻る)
sudo sysctl -w net.core.somaxconn=4096

# 永続的な変更
# /etc/sysctl.conf または /etc/sysctl.d/99-tcp-tuning.conf に追記:
# net.core.somaxconn = 4096
# net.ipv4.tcp_tw_reuse = 1

# 設定を反映
sudo sysctl -p

まとめ

学んだこと

  • TCP/IPプロトコルスタックの構造
  • TCPの主要機能(信頼性、順序保証、フロー制御等)
  • 3ウェイハンドシェイクと4ウェイハンドシェイク
  • TCP状態遷移とTIME_WAITの意味
  • ポート番号とソケットの概念
  • TCPとUDPの違いと使い分け
  • HTTP、JDBC、Redis等がTCPをどう使うか
  • コネクションプールの仕組みと重要性
  • TCPの確認コマンドとチューニング

重要なポイント

1. TCPの信頼性
- ACKによる到達確認と再送制御
- シーケンス番号による順序保証
- チェックサムによる整合性検証

2. 接続管理
- 3ウェイハンドシェイク(SYN → SYN+ACK → ACK)
- 4ウェイハンドシェイク(FIN → ACK → FIN → ACK)
- TIME_WAIT状態の2MSL待機

3. アプリケーションとTCPの関係
- HTTP/1.1 Keep-Alive: 1つのTCP接続で複数リクエスト
- HTTP/2: 1つのTCP接続で多重化
- JDBC: コネクションプールでTCP接続を再利用
- 接続確立コストは大きい → プール化が重要

4. 運用上の考慮点
- TIME_WAIT滞留の監視
- コネクションプールサイズの適切な設定
- Keep-Aliveによるアイドル接続の管理

チェックリスト

□ netstat/ss コマンドで接続状態を確認できる
□ TIME_WAIT状態の意味と対処法を理解している
□ TCPとUDPの使い分けを判断できる
□ HTTPがTCPをどう使っているか説明できる
□ コネクションプールの必要性を説明できる
□ 接続トラブル時の診断手順を理解している

次のステップ

参考リンク