TCPとUDPはどちらもTCP/IPのトランスポート層を担うプロトコルで、違いをひと言でいえば「TCPは届いたことを確認しながら送る、UDPは確認せず送りっぱなしにする」です。この違いは概念の話ではなく、パケットを覗けばそのまま目に見えます。この記事では図解だけで終わらせず、Docker上に隔離ネットワークを組んでtcpdump・ss・ncの実際の出力を並べ、同じデータを送ったときにTCPとUDPで何がどう変わるのかをバイト単位で確かめていきます。
先に、この記事で実測した結果をまとめておきます。いずれも後半で出力そのものを掲載します。
| 実測した項目 | TCP | UDP |
|---|---|---|
| 5バイト送るのに流れたパケット数 | 8個 | 1個 |
| 同じくフレーム総バイト数 | 549バイト | 47バイト |
| ヘッダのサイズ(実測) | 32バイト | 8バイト |
| 並べ替えが起きる回線での受信順 | 送信順どおり | バラバラ |
ss -tuln での待ち受け状態 | LISTEN | UNCONN |
| 閉じたポートへ接続したとき | 即座に拒否される | 成功に見えることがある |
TCPとUDPの違いを一覧で比較
細かい仕組みに入る前に、両者の性質を整理します。
| 比較項目 | TCP | UDP |
|---|---|---|
| 正式名称 | Transmission Control Protocol | User Datagram Protocol |
| 接続方式 | コネクション型(事前に接続を確立) | コネクションレス型(いきなり送る) |
| 到達保証 | あり(確認応答と再送) | なし(ベストエフォート) |
| 順序保証 | あり | なし |
| フロー制御・輻輳制御 | あり | なし |
| ヘッダサイズ | 20バイト以上(オプションで可変) | 8バイト固定 |
| 速度・遅延 | 確認応答の分だけ遅くなる | 低遅延 |
| 1対多の通信 | 不可(1対1のみ) | ブロードキャスト・マルチキャスト可 |
| 主な用途 | Web・メール・SSH・ファイル転送 | DNS・動画/音声・ゲーム・SNMP |
| IPヘッダ上の番号 | 6 | 17 |
よく「TCPは信頼性が高く、UDPは速い」とまとめられますが、UDPが速いのは「機能が少ないから」であって、回線が速くなるわけではありません。UDPは到達確認も順序整理もアプリケーション側の責任になります。この「責任の置き場所が違うだけ」という感覚が、次の実測で掴めるはずです。
検証環境
以降の出力はすべて、次の環境で実際に取得したものです。自宅や職場のLANでキャプチャすると実際のIPアドレスやMACアドレスが記録に残るため、Dockerで外部から隔離したネットワークを作って検証しています。手元で再現する場合も同じ方法をおすすめします。
# 検証用イメージ(alpine 3.20 に検証ツールを追加)
FROM alpine:3.20
RUN apk add --no-cache tcpdump iproute2 netcat-openbsd python3 iptables
CMD ["sleep", "infinity"]
docker build -t tcpudp-lab .
docker network create --subnet 172.31.0.0/24 tcpudp-net
docker run -d --name srv --network tcpudp-net --ip 172.31.0.10 \
--cap-add NET_ADMIN --cap-add NET_RAW tcpudp-lab
docker run -d --name cli --network tcpudp-net --ip 172.31.0.20 \
--cap-add NET_ADMIN --cap-add NET_RAW tcpudp-lab
172.31.0.10をサーバー役(srv)、172.31.0.20をクライアント役(cli)とします。tcpdumpでパケットを覗いたりtcで回線品質を劣化させたりするため、--cap-add NET_ADMINと--cap-add NET_RAWが必要です。
【実測1】同じ5バイトを送るのに、TCPは8パケット・UDPは1パケット
まず、まったく同じ「HELLO」という5バイトを、TCPとUDPそれぞれで1回送ります。サーバー側でtcpdumpを動かしながら受信します。
TCPで送った場合
# サーバー側:キャプチャと待ち受けを開始
tcpdump -i eth0 -n "tcp port 9000" -w /tmp/tcp.pcap &
nc -l -p 9000 > /tmp/tcp_recv.txt &
# クライアント側:5バイト送る
echo -n "HELLO" | nc -q 1 172.31.0.10 9000
キャプチャを読み出した結果が次の8行です。
$ tcpdump -r /tmp/tcp.pcap -n
172.31.0.20.53646 > 172.31.0.10.9000: Flags [S], seq 701397311, length 0
172.31.0.10.9000 > 172.31.0.20.53646: Flags [S.], seq 1776532157, ack 701397312, length 0
172.31.0.20.53646 > 172.31.0.10.9000: Flags [.], ack 1, length 0
172.31.0.20.53646 > 172.31.0.10.9000: Flags [P.], seq 1:6, ack 1, length 5
172.31.0.10.9000 > 172.31.0.20.53646: Flags [.], ack 6, length 0
172.31.0.20.53646 > 172.31.0.10.9000: Flags [F.], seq 6, ack 1, length 0
172.31.0.10.9000 > 172.31.0.20.53646: Flags [F.], seq 1, ack 7, length 0
172.31.0.20.53646 > 172.31.0.10.9000: Flags [.], ack 2, length 0
実際にデータが入っているのは4行目の1個(length 5)だけで、残りの7個は「段取り」と「後片付け」です。フラグの意味を対応させると、TCPの動きがそのまま読み取れます。
| 行 | フラグ | 意味 |
|---|---|---|
| 1〜3 | [S] → [S.] → [.] | 3ウェイハンドシェイク(SYN → SYN+ACK → ACK)で接続を確立 |
| 4 | [P.] | データ本体の送信(PSH+ACK、length 5) |
| 5 | [.] | サーバーからの確認応答(ack 6=5バイト受け取った) |
| 6〜8 | [F.] → [F.] → [.] | FINによる接続の切断処理 |
5行目のack 6に注目してください。「6バイト目から先を待っている」=「5バイト目までは確実に受け取った」という宣言で、これがTCPの到達保証の実体です。この応答が返ってこなければ、送信側は同じデータを再送します。
UDPで送った場合
# サーバー側
tcpdump -i eth0 -n "udp port 9000" -w /tmp/udp.pcap &
nc -u -l -p 9000 > /tmp/udp_recv.txt &
# クライアント側
echo -n "HELLO" | nc -u -w 1 172.31.0.10 9000
$ tcpdump -r /tmp/udp.pcap -n
172.31.0.20.55683 > 172.31.0.10.9000: UDP, length 5
1行だけです。接続の確立もなければ、受け取ったという返事もありません。それでもサーバー側のファイルにはHELLOが届いています。
流れた総バイト数を数える
キャプチャからフレームの総バイト数を合計すると、差はさらにはっきりします。
TCP: 549 bytes / 8 packets
UDP: 47 bytes / 1 packets
同じ5バイトを届けるのに、TCPはUDPの約11.7倍の通信量を使っています。「UDPは軽い」というのは、この差のことです。
ただし誤解しないでください。この比率は「5バイトだけ送る」という極端な条件だからこそ大きく開きます。ハンドシェイクと切断は接続ごとに1回しか発生しないため、1MBのファイルを転送するようなケースでは、TCPのオーバーヘッドは全体の1%にも届きません。UDPが有利なのは、DNS問い合わせのように「小さなデータを1往復で終わらせたい」場面です。
【実測2】ヘッダを16進ダンプでバイト単位に数える
「TCPヘッダは20バイト以上、UDPヘッダは8バイト」という説明は必ず出てきますが、実際に数えてみると理解が変わります。tcpdump -XXでフレーム全体を16進表示します。
UDPパケットの中身(全47バイト)
$ tcpdump -r /tmp/udp.pcap -n -e -XX
length 47: 172.31.0.20.55683 > 172.31.0.10.9000: UDP, length 5
0x0000: fea2 8b32 9e98 060e 3906 355f 0800 4500 ...2....9.5_..E.
0x0010: 0021 d790 4000 4011 0adf ac1f 0014 ac1f .!..@.@.........
0x0020: 000a d983 2328 000d 587b 4845 4c4c 4f ....#(..X{HELLO
先頭から順に区切ると、47バイトの内訳はこうなります。
| 位置 | 中身 | サイズ | 読み方 |
|---|---|---|---|
0x00〜0x0d | Ethernetヘッダ | 14 | 宛先MAC・送信元MAC・0800(IPv4) |
0x0e〜0x21 | IPヘッダ | 20 | 45=IPv4/ヘッダ20バイト、11=プロトコル番号17(UDP) |
0x22〜0x29 | UDPヘッダ | 8 | d983 2328 000d 587b |
0x2a〜0x2e | データ本体 | 5 | 4845 4c4c 4f=HELLO |
UDPヘッダの8バイトは、これだけしかありません。
| 16進 | 10進 | フィールド |
|---|---|---|
d983 | 55683 | 送信元ポート番号 |
2328 | 9000 | 宛先ポート番号 |
000d | 13 | 長さ(ヘッダ8+データ5) |
587b | — | チェックサム |
「相手のポート番号」と「長さ」と「チェックサム」だけ。順序番号も確認応答番号もありません。UDPが順序を保証できないのは意地悪をしているのではなく、そもそも順序を表す欄がヘッダに存在しないからです。
IPアドレスも読み取れます。ac1f 0014はac=172、1f=31、00=0、14=20なので172.31.0.20、続くac1f 000aが172.31.0.10です。指定したコンテナのIPと一致します。
TCPパケットの中身(全71バイト)
$ tcpdump -r /tmp/tcp.pcap -n -e -XX "tcp[tcpflags] & tcp-push != 0"
length 71: 172.31.0.20.53646 > 172.31.0.10.9000: Flags [P.], length 5
0x0000: fea2 8b32 9e98 060e 3906 355f 0800 4500 ...2....9.5_..E.
0x0010: 0039 d0e2 4000 4006 1180 ac1f 0014 ac1f .9..@.@.........
0x0020: 000a d18e 2328 29ce 7940 69e3 babe 8018 ....#().y@i.....
0x0030: 01f6 5888 0000 0101 080a f7ca 5545 f9b0 ..X.........UE..
0x0040: b133 4845 4c4c 4f .3HELLO
データは同じHELLOの5バイトなのに、フレーム全体は47バイトから71バイトへ、24バイト増えています。増えた分はすべてTCPヘッダです。
0x0eのIPヘッダにある06がプロトコル番号6(TCP)。UDPの11(17)と入れ替わっている0x22からがTCPヘッダで、d18e=53646(送信元ポート)、2328=9000(宛先ポート)- 続く
29ce 7940がシーケンス番号、69e3 babeが確認応答番号。UDPには存在しなかった欄 8018の上位1桁8がデータオフセットで、8×4=32バイトがTCPヘッダ長。残りの018がフラグで、0x10(ACK)+0x08(PSH)=0x1801f6=502がウィンドウサイズ。フロー制御に使われる欄で、これもUDPにはない
TCPヘッダは固定20バイトですが、今回は末尾に12バイトのオプション(0101 080a ...=NOP・NOP・タイムスタンプ)が付いて32バイトになっています。「20バイト以上」と説明される理由がここで確認できます。
IPヘッダの全長フィールドで検算もできます。UDPは0021=33=20(IP)+8(UDP)+5(データ)、TCPは0039=57=20(IP)+32(TCP)+5(データ)。それぞれEthernetヘッダ14バイトを足すと、47バイトと71バイトにぴたりと一致します。
【実測3】順序は本当に保証されるのか
「TCPは順序を保証する」を確かめます。通常のLANはほぼ順序どおり届いてしまうので、tcのnetemでクライアントの送出に意図的な並べ替えを発生させます。
# クライアント側:50msの遅延と50%の並べ替えを注入
tc qdisc add dev eth0 root netem delay 50ms reorder 50% 50%
この状態で、001から010まで番号を振ったデータを順番に10個送り、サーバー側が受け取った順序を表示します。
送信順: 001 002 003 004 005 006 007 008 009 010
UDP受信順: 001 002 003 004 005 007 010 006 008 009
TCP受信順: 001 002 003 004 005 006 007 008 009 010
UDPは6番目以降が入れ替わり、TCPは完全に送信順のままです。同じ回線・同じ並べ替え設定で結果が分かれました。
ここで重要なのは、TCPでもパケット自体は並べ替えられているという点です。TCPは受信側でシーケンス番号を見てバッファに並べ直し、順序が揃った分だけをアプリケーションに渡します。実測2で見た29ce 7940というシーケンス番号が、まさにこの並べ直しに使われています。UDPヘッダにはこの番号がないため、届いた順にそのままアプリケーションへ渡すしかありません。
したがって「UDPを使うと順序が狂う」のではなく、「順序を直す仕事がアプリケーション側に回ってくる」が正しい理解です。実際、UDPを使うオンラインゲームや音声通話は、アプリケーション独自に連番を振って対処しています。
検証が終わったら、注入した劣化設定は必ず戻してください。
tc qdisc del dev eth0 root
TCPの仕組み|なぜ確実に届くのか
実測で見た動きを、機能として整理しておきます。
- 3ウェイハンドシェイク:SYN → SYN+ACK → ACKの3往復で、双方が「送れる・受け取れる」状態にあることを確認してから通信を始める
- 確認応答(ACK)と再送制御:受信側は「次に何バイト目を待っているか」を返す。一定時間ACKが返らなければ送信側が再送する
- 順序制御:シーケンス番号で並べ直してからアプリケーションに渡す
- フロー制御:ウィンドウサイズで「あとどれだけ受け取れるか」を伝え、受信側の処理能力を超えないよう調整する
- 輻輳制御:ネットワークが混雑してパケットロスが増えると、送信ペースを自動的に落とす
この5つが揃っているため、TCPを使うアプリケーションは「送ったデータは順番どおり全部届く」という前提でコードを書けます。その代わり、接続の確立に最低1往復(RTT)の時間がかかり、パケットロスが起きると再送待ちで一時的に止まります。動画や音声で「少し欠けてもいいから止まらないでほしい」場合にTCPが不利になるのは、この止まる性質のためです。
UDPの仕組み|なぜ速いのか
UDPがやることは、実測2で見たとおり「ポート番号を付けて、長さとチェックサムを添えて送り出す」だけです。
- 接続の確立がないので、送りたいときに即座に送れる(ハンドシェイクの往復がゼロ)
- 確認応答を待たないので、パケットが失われても止まらない
- ヘッダが8バイト固定なので、小さいデータほど効率が良い
- 宛先を1つに固定しないため、ブロードキャストやマルチキャストで一斉送信できる(TCPは1対1のみ)
DNSがUDPを使う理由もここにあります。名前解決は「短い質問を投げて短い答えをもらう」だけなので、3ウェイハンドシェイクに1往復かけていては本末転倒です。実測1で見たとおり、TCPなら8パケット必要な処理がUDPなら往復2パケットで済みます。
どちらが使われているかをサーバーで確認する
「このサービスはTCPかUDPか」は暗記しなくても、Linuxサーバー上で調べられます。
/etc/services で割り当てを引く
$ grep -E '^(ssh|domain|http|https|ntp|snmp|tftp|syslog)[[:space:]]' /etc/services
ssh 22/tcp # SSH Remote Login Protocol
domain 53/tcp # Domain Name Server
domain 53/udp
tftp 69/udp
http 80/tcp www
ntp 123/udp # Network Time Protocol
snmp 161/tcp # Simple Net Mgmt Protocol
snmp 161/udp
https 443/tcp # http protocol over TLS/SSL
https 443/udp # HTTP/3
syslog 514/udp
この出力から、暗記より役に立つ事実が3つ読み取れます。
domain(DNS)は53番がTCPとUDPの両方に登録されている。通常はUDPを使い、応答が大きい場合やゾーン転送ではTCPに切り替わるsnmpも161番がTCP/UDP両方。実運用ではUDPが使われるhttpsに443/udpがある。これがHTTP/3(QUIC)で、UDPの上に信頼性の仕組みを自前で載せた新しい方式
最後の項目は、「TCPかUDPか」が固定的な二択ではなくなってきている実例です。HTTP/3はTCPをやめてUDPを土台に選び、再送や順序制御をQUICというプロトコルで実装しています。「UDPは信頼性がない」のではなく「信頼性の仕組みを誰が持つかを選べる」ということです。
dig でDNSがどちらを使ったか確かめる
digは使用したプロトコルを結果に表示してくれます。
$ dig @8.8.8.8 google.com | grep -E "SERVER:|MSG SIZE"
;; SERVER: 8.8.8.8#53(8.8.8.8) (UDP)
;; MSG SIZE rcvd: 135
$ dig +tcp @8.8.8.8 google.com | grep -E "SERVER:|MSG SIZE"
;; SERVER: 8.8.8.8#53(8.8.8.8) (TCP)
;; MSG SIZE rcvd: 135
デフォルトでは(UDP)、+tcpを付けると(TCP)と表示され、同じ53番ポートでどちらも通ることが確認できます。ファイアウォールでDNSのTCP 53番を閉じてしまい、応答が大きいときだけ名前解決に失敗するというトラブルは、この仕組みを知らないと原因にたどり着けません。
ss で待ち受け中のポートを一覧する
サーバー上で実際に開いているポートはss -tulnで確認します。-tがTCP、-uがUDP、-lが待ち受け中、-nが名前解決の抑制です。
$ ss -tuln
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
udp UNCONN 0 0 0.0.0.0:9000 0.0.0.0:*
tcp LISTEN 0 1 0.0.0.0:9000 0.0.0.0:*
同じ9000番でTCPとUDPの両方を待ち受けた状態です。State欄がTCPはLISTEN、UDPはUNCONN(未接続)と明確に分かれます。UDPには「接続を待つ」という概念がないため、LISTENという状態が存在しないのです。
TCPで実際に接続が張られると、状態を持つことがさらにはっきりします。
$ ss -tan state all '( sport = :9000 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 1 0.0.0.0:9000 0.0.0.0:*
ESTAB 0 0 172.31.0.10:9000 172.31.0.20:59048
接続元ごとにESTAB(確立済み)の行が増えていきます。TCPはLISTEN・SYN-SENT・ESTABLISHED・TIME-WAITなどの状態を遷移しますが、UDPにはこの状態遷移そのものがありません。
なおss -uanでUDPソケットがESTABと表示されることがありますが、これは通信相手が確立したという意味ではありません。アプリケーションがそのソケットに宛先を固定した(connect()した)というローカルな状態を示しているだけで、ネットワーク上に接続が存在するわけではないので混同しないよう注意してください。
【要注意】UDPのポート開放確認は誤判定する
ここが実務で最もハマるポイントです。「ポートが開いているか」の確認方法は、TCPとUDPで信頼度がまったく違います。
TCPは明確に拒否される
何も待ち受けていない9999番へ接続してみます。
$ nc -zv -w 2 172.31.0.10 9999
nc: connect to 172.31.0.10 port 9999 (tcp) failed: Connection refused
終了ステータス: 1
このときサーバー側では、TCPがRSTフラグを立てたパケットを返しています。
172.31.0.20.36960 > 172.31.0.10.9999: Flags [S], seq 713531340, length 0
172.31.0.10.9999 > 172.31.0.20.36960: Flags [R.], ack 713531341, win 0, length 0
Connection refusedが出たら「サーバーには届いているが、そのポートで何も動いていない」と断定できます。TCPは閉じていることをプロトコル自身が教えてくれるわけです。
UDPは「閉じている」を伝える手段がない
同じ9999番にUDPで送ると、返ってくるのはUDPではなくICMPのport unreachableです。
172.31.0.20.60632 > 172.31.0.10.9999: UDP, length 1
172.31.0.10 > 172.31.0.20: ICMP 172.31.0.10 udp port 9999 unreachable, length 37
つまりUDPの疎通確認はICMPが返ってくることに依存しています。ところが、ICMPはセキュリティ上の理由でファイアウォールに落とされることが非常に多いプロトコルです。そこで、ICMPをブロックした状態で同じ確認をしてみます。
# サーバー側でICMPのport unreachableだけを落とす
iptables -A OUTPUT -p icmp --icmp-type port-unreachable -j DROP
$ nc -zvu -w 2 172.31.0.10 9999
Connection to 172.31.0.10 9999 port [udp/*] succeeded!
終了ステータス: 0
# 比較:同じ状況でTCPは正しく失敗する
$ nc -zv -w 2 172.31.0.10 9999
nc: connect to 172.31.0.10 port 9999 (tcp) failed: Connection refused
終了ステータス: 1
9999番では何ひとつ待ち受けていないのに、succeeded!と表示され、終了ステータスまで0(成功)になりました。UDPは応答が返らないのが正常な状態なので、「拒否されなかった=開いている」と判定するしかなく、ICMPが遮断されると開いているポートと区別がつかなくなるためです。
nmapがUDPスキャンの結果をopen|filteredという曖昧な表記で返すのも同じ理由です。UDPポートの疎通を確認したいときは、次のように進めてください。
- サーバー側で
ss -ulnを実行し、そのポートで待ち受けているプロセスが実在するか確認する(これが最も確実) - サーバー側で
tcpdump -i any -n "udp port ポート番号"を動かし、クライアントからのパケットが実際に到達しているか見る nc -zvuの成功だけを根拠にしない。そのサービスが理解できる本物のリクエストを送り、応答が返るかで判断する(DNSならdig、NTPならntpdate -qなど)
検証後は追加したルールを削除しておきます。
iptables -D OUTPUT -p icmp --icmp-type port-unreachable -j DROP
よくあるトラブルと切り分け手順
| 症状 | 疑うところ | 確認コマンド |
|---|---|---|
SSHがすぐConnection refused | TCP 22番でsshdが起動していない | ss -tlnp | grep :22 |
| SSHが応答せずタイムアウト | 途中のファイアウォールで破棄されている | tcpdump -i any -n "tcp port 22" |
| 短い名前解決は通るが特定ドメインだけ失敗 | 応答が大きくTCP 53番へ切り替わっている | dig +tcp @DNSサーバー ドメイン |
| SNMPで機器を監視できない | UDP 161番。ICMP遮断で確認も誤判定しやすい | 機器側でss -ulnp | grep :161 |
| 音声・映像が途切れる | UDPのためロスがそのまま品質に出る | ping -c 100でロス率を測る |
| ファイル転送だけ極端に遅い | TCPの再送。ロスがあると速度が落ちる | ss -tiでretransを確認 |
切り分けの原則はシンプルです。TCPなら「拒否された(refused)」か「無反応(timeout)」かで、サービス停止とファイアウォール遮断を切り分けられます。UDPではこの区別ができないので、必ずサーバー側からssとtcpdumpで確認してください。
TCP・UDPはTCP/IPのどこに位置するのか
TCPとUDPはどちらも、TCP/IPの4階層モデルにおけるトランスポート層のプロトコルです。その下のインターネット層をIPが担当し、上のアプリケーション層をHTTPやDNSが担当します。この位置関係が分かると、「なぜTCPだけが再送できるのか」「なぜどちらもポート番号を使うのか」が整理しやすくなります。
実測2で16進ダンプを層ごとに区切れたのも、この階層構造がそのままバイト列の並びになっているからです。階層全体の役割と、パケットロス環境での両者の比較はTCP/IPとは?4階層の役割と通信の流れをコマンド出力で解説にまとめています。20%のパケットロス下でTCPは完全なデータを届ける一方、UDPは200個中162個しか届かないという実測値も掲載しています。
TCPとUDPの使い分け
ここまでの実測を踏まえると、選択の基準は「速度か信頼性か」よりも「失われたデータをどう扱いたいか」で考えるほうが的確です。
| データが失われたときにどうしたいか | 選ぶべきもの | 例 |
|---|---|---|
| 時間がかかっても再送して完全に揃えたい | TCP | Web・メール・SSH・ファイル転送・データベース |
| 古いデータを再送されても意味がない。今の状態が欲しい | UDP | 音声通話・ライブ配信・オンラインゲーム |
| 失敗したらアプリ側で投げ直すほうが速い | UDP | DNS問い合わせ・SNMP・NTP |
| 多数の相手へ一斉に送りたい | UDP | DHCP・マルチキャスト配信 |
| 信頼性は必要だが接続確立の待ち時間も惜しい | UDP+QUIC | HTTP/3 |
「1秒前の音声が今さら届いても再生できない」——この一点がUDPを選ぶ最大の理由です。逆にファイル転送では1バイトの欠けも許されないため、TCPの再送待ちは必要なコストになります。
よくある質問
TCPとUDPはどちらが速いですか?
1回のやり取りにかかる時間はUDPのほうが短くなります。実測1のとおりTCPは接続確立と切断で余分に7パケットを使うためです。ただし回線の帯域そのものは変わらないため、大きなファイルを転送する速度がUDPで劇的に速くなるわけではありません。差が大きく出るのは、DNSのように小さなデータを1往復で終わらせる用途です。
同じポート番号をTCPとUDPで同時に使えますか?
使えます。実測でも9000番をTCPとUDPで同時に待ち受け、ss -tulnにLISTENとUNCONNの2行が並びました。ポート番号はプロトコルごとに独立した空間なので、TCP 53番とUDP 53番は別物として扱われます。ファイアウォールの設定でも、TCPとUDPは個別に許可する必要があります。
UDPはデータが壊れても分からないのですか?
チェックサムがあるため、壊れていること自体は検出できます。実測2のUDPヘッダにも587bというチェックサム欄がありました。ただし検出した後に再送を要求する仕組みがないため、壊れたデータは黙って破棄されます。「壊れたと気づけるが、直せない」というのが正確な理解です。
nc -zvu が成功したのにサービスに繋がりません
UDPの疎通確認は誤判定するためです。本記事で実測したとおり、ICMPが遮断されている環境では何も待ち受けていないポートでもsucceeded!と表示されます。サーバー側でss -ulnpを実行してプロセスが実在するか確認するか、そのサービスが解釈できる本物のリクエストを送って応答を確かめてください。
HTTP/3がUDPを使うのはなぜですか?
TCPの再送制御が、複数の通信を1本の接続にまとめたときに足を引っ張るためです(1つのパケットロスで全体が待たされる問題)。HTTP/3はUDPを土台にして、再送や順序制御をQUICというプロトコルで作り直し、影響範囲をストリームごとに限定しています。/etc/servicesにhttps 443/udpが登録されているのはこのためです。
tcpdump を使うのに root 権限は必要ですか?
必要です。パケットキャプチャにはネットワークインターフェースを直接読む権限(CAP_NET_RAW)が要ります。本記事のDocker環境で--cap-add NET_RAWを付けているのはこのためです。本番サーバーで安易にキャプチャすると通信内容が平文で保存される点にも注意してください。検証は隔離環境で行うのが安全です。
まとめ
TCPとUDPの違いは、実際にパケットを見ると次の3点に集約されます。
- 手続きの量が違う:同じ5バイトを送るのにTCPは8パケット549バイト、UDPは1パケット47バイト
- ヘッダに持っている情報が違う:TCPヘッダ32バイトにはシーケンス番号・確認応答番号・ウィンドウサイズがあり、UDPヘッダ8バイトにはポート・長さ・チェックサムしかない。順序保証や再送ができるかどうかは、この欄の有無で決まっている
- 確認方法の信頼度が違う:TCPは
Connection refusedで閉じていると断定できるが、UDPはnc -zvuが誤って成功を返す
サーバー運用の現場では、まずss -tulnでTCPとUDPのどちらで待ち受けているかを確認し、疑わしいときはtcpdumpでパケットが本当に届いているかを見る、という順序で切り分けると迷いません。特にUDPは「応答がないのが正常」なので、クライアント側のツールの結果だけを信用しないことが重要です。
関連記事
- TCP/IPとは?4階層の役割と通信の流れをコマンド出力で解説|TCPとUDPが位置するトランスポート層を含めた全体像
- ss – ソケットの状態を表示するコマンド|
LISTEN・ESTABなどの状態とオプションの詳細 - netcat – ネットワーク接続を読み書きするコマンド|本記事で使った
ncの使い方 - Linuxのポート確認方法とポート競合の解消手順|使用中のポートを特定して競合を解消する
- 接続不良の原因を特定!ネットワークトラブルシュートの基本コマンド一覧|切り分けの全体手順
- DNSサーバーとは?仕組み・種類・確認方法をコマンドで解説|UDP 53番を使うDNSの仕組み
- iptables – Linux パケットフィルタリング設定コマンド|TCP/UDPを個別に許可・遮断する設定

