「ここは TCP? それとも UDP?」——API を1本足すたびに毎回考える問いではありません。ほとんどの Web/API は HTTP(=TCP)でよく、深く悩む必要はない。けれどリアルタイム音声を載せる、ゲームの同期を作る、DNS を叩く、HTTP/3 に乗るべきか判断する——そういう場面で「なんとなく TCP」を選ぶと、レイテンシで詰みます。逆に「速そうだから UDP」で信頼性を自作し始めると、TCP を劣化再実装する泥沼にはまります。
この記事は、TCP と UDP の違いを IETF の一次情報で正確に押さえ、「どちらを、なぜ選ぶか」を意思決定フローとして使える形にまとめます。仕組みの詳細はTCP の仕組み、全体像はTCP/IP 完全ガイドを参照してください。
この記事のルール:規定は TCP = RFC 9293(2022年8月)、UDP = RFC 768(1980年)、QUIC = RFC 9000(2021年)、HTTP/3 = RFC 9114(2022年) に基づきます。コードは Node.js 標準ライブラリで動く形に整えています。最新仕様は rfc-editor.org で確認してください。
1. 一枚で押さえる:TCP と UDP の本質的な違い
両者の差は「機能の多寡」ではなく、信頼性という責務を誰が引き受けるかの設計思想の違いです。
| 観点 | TCP(RFC 9293) | UDP(RFC 768) |
|---|---|---|
| 接続 | コネクション指向(3ウェイハンドシェイク) | コネクションレス(いきなり送る) |
| 配送保証 | あり(ACK+再送で回復) | なし(投げっぱなし) |
| 順序保証 | あり(シーケンス番号で整列) | なし(入れ替わりうる) |
| 重複排除 | あり | なし |
| データ境界 | 保たない(バイトストリーム) | 保つ(1 send = 1 データグラム) |
| フロー制御 | あり(受信ウィンドウ) | なし |
| 輻輳制御 | あり(必須・AIMD) | なし(アプリの責任) |
| ヘッダ | 最小20バイト | 8バイト |
| 速度特性 | 確立RTT+HoLB の固定費 | 最小オーバーヘッド・低遅延 |
| 代表用途 | HTTP/HTTPS, DB, SSH, gRPC, メール | DNS, 音声/映像, ゲーム, QUIC, syslog |
この表の1行に集約すると:TCP は「正しさ」を、UDP は「速さと制御の自由」を、デフォルトで選んでいます。
1.1 「データ境界」の違いは見落とされがち
配送保証ばかり注目されますが、実務でバグを生むのは境界です。
- TCP はバイトストリーム:
write("AB")とwrite("CD")が、相手では"ABCD"1回で届くかもしれない。メッセージの区切りはアプリが自前で実装(長さプレフィックス等)する必要がある。 - UDP はメッセージ指向:1回の
sendが、相手ではちょうど1回のmessageとして(届けば)境界を保って受け取れる。
「UDP の方が扱いやすい」と感じる場面があるのはこのため。ただし届く保証がない代償とのトレードオフです。
2. なぜ UDP を選ぶのか——「再送が害になる」領域がある
「保証がないなら TCP でいいのでは?」という直感は、ある重要な事実を見落としています。リアルタイム領域では、失われた古いデータを再送して待つより、捨てて次に進む方が正しいのです。
2.1 リアルタイム音声・映像
20ms 前の音声フレームが今になって再送されても、再生位置はとうに過ぎていて使い道がない。TCP は律儀に再送し、しかもHoLBで後続の新しいフレームまで足止めする——これは音切れ・遅延として体感品質を悪化させます。UDP(+アプリ層のジッタバッファや FEC)なら「落ちたフレームは諦め、最新を優先」できる。WebRTC が UDP を土台にする理由です。
2.2 DNS——1往復で終わる小さな問い合わせ
DNS クエリは「小さな質問→小さな答え」。ここで TCP の3ウェイハンドシェイク(1RTTの確立)を毎回払うのは割に合いません。UDP なら1往復で完結します(応答が大きい/切り詰められた場合は TCP にフォールバック)。確立コストを払う価値があるほどの会話量がないのが UDP 向きの典型です。
2.3 オンラインゲームの状態同期
「100ms 前のプレイヤー座標」より「最新の座標」が常に正しい。順序通りの完全な履歴は不要で、最新性が命。UDP に独自の軽量な信頼性(重要イベントだけ ACK する等)を載せる設計が定石です。
共通する判断軸:「鮮度(最新性)> 完全性(取りこぼさないこと)」が成り立つなら UDP が候補になります。逆に「1バイトでも欠けたら困る」「順序が崩れたら破綻する」なら TCP です。
3. 意思決定フロー——実際にどう選ぶか
迷ったときに上から順に当てる、実務用のフローです。
Q1. 確実な配送と順序が必須か?(1バイトの欠落・順序崩れも許されない)
├─ YES → TCP。さらに「アプリ意味」が要るなら HTTP/gRPC(=TCP の上)。 → 終了
└─ NO → Q2 へ
Q2. 低遅延・最新性が最優先で、損失をアプリで許容/補償できるか?
├─ NO → 判断保留なら TCP を既定に。 → 終了
└─ YES → Q3 へ
Q3. 「UDP だが信頼性・暗号・多重化が欲しい」か?
├─ YES → QUIC(HTTP/3)に乗る。生UDPで信頼性を自作しない。 → 終了
└─ NO → Q4 へ
Q4. 純粋に最小オーバーヘッドの片方向/小往復通信か?(音声/映像/ゲーム/DNS/メトリクス)
└─ YES → UDP + アプリ層で必要最小限の補償(FEC・選択的ACK・ジッタバッファ)。
最重要の指針:迷ったら TCP(または HTTPS/gRPC)が既定です。UDP は「鮮度 > 完全性」が明確で、損失への対処を設計できるときの最適化であって、デフォルトではありません。そして「UDP の速さ × 信頼性」が欲しいなら、自作ではなくQUIC に乗るのが2026年の現実解です。
4. コードで見る差:同じ「エコー」を TCP と UDP で
抽象論を具体に落とします。同じ機能を両者で書くと、設計思想の差がそのままコードに出ます。
4.1 TCP:接続を張り、境界は自前で区切る
import net from "node:net";
// TCP はバイトストリーム。改行などで「メッセージ境界」を自分で復元する必要がある
const server = net.createServer((socket) => {
let buffer = "";
socket.setEncoding("utf8");
socket.on("data", (chunk: string) => {
buffer += chunk;
let nl: number;
// 改行区切りでフレーミング(write と data は1対1でないため必須)
while ((nl = buffer.indexOf("\n")) >= 0) {
const line = buffer.slice(0, nl);
buffer = buffer.slice(nl + 1);
socket.write(`${line}\n`); // echo
}
});
});
server.listen(9000);
4.2 UDP:接続なし・境界つき・届く保証なし
import dgram from "node:dgram";
// UDP は 1 send = 1 message。境界は保たれるが、順序・到達・重複は保証されない
const server = dgram.createSocket("udp4");
server.on("message", (msg: Buffer, rinfo) => {
// フレーミング不要(メッセージ指向)。ただし「届かなかった message」は永遠に来ない
server.send(msg, rinfo.port, rinfo.address);
});
server.bind(9001);
コードに思想が出ています:TCP は「接続管理+フレーミング」というコストを払って完全性を得る。UDP はそれを払わない代わりに、信頼性が要るなら自分で設計することになります。
4.3 もし UDP で「最低限の信頼性」を足すなら
UDP で重要メッセージだけ確実に届けたい場合の最小設計(これがいかに面倒かを知ることが、QUIC を選ぶ判断につながる)。
import dgram from "node:dgram";
/** 重要メッセージにシーケンス番号を付け、ACK が来るまで指数バックオフで再送する最小実装 */
class ReliableUdpSender {
private seq = 0;
private readonly pending = new Map<number, NodeJS.Timeout>();
constructor(
private readonly socket: dgram.Socket,
private readonly port: number,
private readonly host: string,
) {
// 受信側からの ACK(seq) を受けて、対応する再送タイマーを止める
socket.on("message", (msg) => {
if (msg[0] === 0x06 /* ACK */) this.clear(msg.readUInt32BE(1));
});
}
send(payload: Buffer, attempt = 0): void {
const seq = attempt === 0 ? this.seq++ : this.lastSeq;
this.lastSeq = seq;
const frame = Buffer.concat([Buffer.from([0x01]), u32(seq), payload]);
this.socket.send(frame, this.port, this.host);
// ACK が来なければ指数バックオフで再送(=TCP の RTO/再送を手で再発明している)
const timer = setTimeout(() => this.send(payload, attempt + 1), 200 * 2 ** attempt);
this.pending.set(seq, timer);
}
private lastSeq = 0;
private clear(seq: number): void {
const t = this.pending.get(seq);
if (t) clearTimeout(t), this.pending.delete(seq);
}
}
const u32 = (n: number): Buffer => { const b = Buffer.alloc(4); b.writeUInt32BE(n); return b; };
これはTCP の再送機構の劣化版を手で再発明しているにすぎません。ここに順序整列・輻輳制御・フロー制御・暗号化まで足すと、結局 TCP か QUIC を作ることになります。だから「UDP で信頼性が欲しい」の答えは、ほぼ常に QUIC です。
5. 第三の選択肢:QUIC / HTTP3——「UDP だが信頼性あり」
TCP の仕組みで見た TCP の構造的弱点——ヘッドオブラインブロッキングと確立RTTの固定費——を克服するために、IETF は Transport 層を作り直しました。それが QUIC(RFC 9000)です。
- UDP の上に実装:OS のTCPスタックやミドルボックスの制約を回避し、ユーザー空間で進化できる。
- ストリーム独立の信頼性:複数ストリームを多重化し、1ストリームのロスが他を止めない(TCPのHoLBを解消)。
- 暗号化が前提(TLS 1.3 統合):ハンドシェイクと暗号鍵交換を融合し、0〜1RTTで接続確立。
- コネクションマイグレーション:IPが変わっても(Wi-Fi↔モバイル)接続を維持できる。
HTTP/3(RFC 9114)はこの QUIC を土台にした HTTP です。つまり「HTTP/3 にする」とは、Transport を TCP から QUIC(UDP) へ載せ替えること。アプリのコードはほぼ変えずに、TCP の固定費と HoLB を外せる——これが QUIC の実務的な価値です。
選定の現実:自前で UDP に信頼性を実装するのは(§4.3 の通り)TCP/QUIC の再発明です。「低遅延 × 信頼性 × 暗号化」が欲しいなら、QUIC ライブラリ/HTTP3 対応に乗るのが正解。生 UDP は、本当に最小オーバーヘッドが要る(音声/映像/ゲーム/メトリクス)か、QUIC が使えない制約下に限ります。
6. よくある誤解を正す
- 「UDP は速い」=半分正しい。確立RTTとHoLBが無い分レイテンシで有利だが、輻輳制御をしないUDPは混雑時にパケットを撒き散らし、かえって全体を悪化させることもある。「速い」は「軽い」であって「常に高性能」ではない。
- 「TCP は重いから避ける」=多くの場合は誤り。Keep-Alive とコネクションプールで確立コストは償却でき、現代のCUBIC/BBRは高性能。まず TCP を正しく使い切るのが先。
- 「UDP だからファイアウォールが楽」=逆のことが多い。コネクションレスゆえステートフルFWやNATとの相性に注意が要る(NATマッピングのタイムアウトでキープアライブが必要等)。
- 「HTTP/3 にすれば必ず速くなる」=条件付き。高ロス・高遅延・モバイルで効果が大きい一方、低遅延な有線環境では差が小さいことも。計測して判断する。
7. まとめ:選択を一文で
確実な配送・順序が要るなら TCP(迷ったらこれ、または HTTPS/gRPC)。低遅延で『鮮度 > 完全性』が成り立つなら UDP。そして『UDP の速さ × 信頼性』が欲しいなら、自作せず QUIC/HTTP3 に乗る。
- TCP=信頼性をネットワークに作らせる。UDP=信頼性をアプリの責任にする(or 要らない)。
- UDP の強みは低遅延・メッセージ境界・制御の自由。弱みは保証ゼロ(自作は茨の道)。
- QUIC は両者の良いとこ取りを標準化した第三の選択肢。HTTP/3 はその応用。
- 既定は TCP、UDP は最適化、QUIC は信頼性つき低遅延の現実解。
プロトコル選択は、レイテンシ・体感品質・実装コストを同時に左右する上流の意思決定です。仕組み(TCP/IP 全体像 / TCP の内部)を踏まえて選べば、後から効いてきます。
私(友田 陽大)は、リアルタイム配信・決済・モバイル連携のバックエンドで、TCP/UDP/QUIC を要件から選定し、低遅延と信頼性を両立する設計を手がけています。「リアルタイム通信の遅延・音切れ」「HTTP/3 に移行すべきか」「UDP で独自プロトコルを作りたいが信頼性が怖い」——こうしたプロトコル選定と実装の課題を、計測と一次情報に基づいて一緒に解きほぐします。お気軽にご相談ください。