連載Index(読む順・公開済リンクはここが最新): S00: 門前の誓い(総合Index)
TCPとUDPの違いが、ふわっとしか説明できない。
HTTPはTCP?オンラインゲームはUDP?じゃあSlackやTeamsは何が動いている?
ポート番号の話になると、急に置いていかれる。
NATとかファイアウォールとか、聞いたことはあるのに言葉が繋がらない。
このページでは「TCP/UDP=何が違うのか」「どのアプリでどう使われるのか」を、
用語→例→手を動かす順で整理する。
このページで手に入るもの
- TCP/UDPの違いを「届き方」と「遅れ方」で説明できるようになる
- ソケット(IPアドレス+ポート番号+TCP/UDP)の見方が分かる(例つき)
- Slack / Teams / IPMsg / オンラインゲーム / Windows Update で「どう使われるか」を整理できる
- 先に逆引き(やりたいこと→TCP/UDP→注意点)が1枚で見える
- C#で最小の通信を試せる:TCPはローカルで接続→送受信、UDPは送受信(コメント多め)
- 有名どころポート一覧(TCP/UDP込み)を用途で引ける+外部一覧リンクも置く
先に逆引き(やりたいこと→TCP/UDP→注意点)
ここは「どっちを選ぶか」を用途から決められる形にする。
| やりたいこと | まず選ぶ | 理由(1行) | 注意点 |
|---|---|---|---|
| Webを見る(HTTP/HTTPS) | TCP | 欠けると困るので再送が効く方が自然 | 企業ネットワークではプロキシが間に入ることがある |
| チャット/業務ツール(Slack/Teams) | TCPが主 | 欠けると困るメッセージを確実に運ぶ | 443/TCPへ集約されやすい。宛先制限やSSL検査で止まることがある |
| LAN内で相手を探す(IPMsgなど) | UDPが出やすい | 一斉通知(ブロードキャスト)と相性が良い | セグメントや設定で届かない。応答が無い構成もある |
| リアルタイム(オンラインゲーム/通話) | UDPが出やすい | 遅延より「今」を優先しやすい | 到達の確定が難しい。必要ならアプリ側で補う |
| ファイル転送/更新配布(Windows Updateなど) | TCP | 欠けると困るデータを運ぶ | 途中断は再開や検証が必要。回線が混むと遅くなる |
| 時刻同期(NTP) | UDPが多い | 小さなパケットを軽くやり取り | 取りこぼし前提で繰り返す設計が多い |
まず揃える用語(ここで止まらないための最小セット)
ここから先で用語が引っかかりやすいので、先に押さえる言葉をまとめる。
「単語が分からない」だけで読み進めづらくなる箇所を先に潰しておく。
ソケット(IPアドレス+ポート番号+TCP/UDP)
通信の窓口。実務では「IPアドレス」「ポート番号」「TCPかUDPか」の組で考える。
同じIP・同じポート番号でも、TCPとUDPは別物になる。
- 例(IPアドレス+ポート番号)
-
172.16.1.1:8080(宛先が 172.16.1.1、ポートが 8080) -
127.0.0.1:18080(ローカルPCの 18080。サンプルの接続先に使う)
-
- 例(プロトコルまで含めて確定する)
TCP 172.16.1.1:8080-
UDP 172.16.1.1:8080(数字が同じでも別物)
.NETでの表現は次のようになる(例)。
using System.Net;
// 例: 172.16.1.1:8080 をエンドポイントとして持つ
var ep = new IPEndPoint(IPAddress.Parse("172.16.1.1"), 8080);
ポート番号
同じPC(同じIP)で複数アプリが通信できるように分ける番号。
“443” のような数字は、TCP/UDPまで含めて初めて意味が確定する。
ファイアウォール(FW)
通信を通す/落とすルール。宛先IP・ポート番号・TCP/UDPの別などで判定する。
番号だけの会話だとズレやすい。
NAT(家庭用ルータでよく出る)
家庭用ルータなどが、内部アドレスを外へ出す時に変換する仕組み。
変換の状態は永続ではなく、時間経過で消えることがある(放置後に途切れる原因になりやすい)。
NATタイプ(ゲーム機や一部サービスで出てくる表示)
ゲーム機や一部サービスでは、接続の“通りやすさ”をタイプで表示することがある。
Sony系の表示(Type 1/2/3)と、Open/Moderate/Strict の表示は意味が近いものとして扱われることが多い。
- Open / Type 1:制限が少ない(NAT無し相当の構成を含む)
- Moderate / Type 2:一般的な家庭用ルータ構成で通る範囲
- Strict / Type 3:制限が強く、相手との組み合わせで繋がりにくいことがある
プロキシ
HTTP/HTTPSの通信を中継する仕組み。Webは通るが、別経路の通信だけ止まる、が起きやすい。
ネットワークの混雑(回線や経路が混み合う状態)
回線や経路が混み合い、遅延が増えたり再送が増えたりする状態。
「夕方だけ遅い」などの形で見えやすい。
TCPとUDPの違い(届き方と遅れ方で整理する)
ここは、TCP/UDPを「運び方の性格」として整理する。
TCP(欠けたら埋める)
TCPは、順序通りに届け、欠けた分は再送で埋める前提がある。
その分、混雑時は待ちが積み上がって遅くなることもあるが、「欠けない」方が大事な通信に合う。
- 接続を張ってから送る(相手の存在を確かめた上で進む)
- 取りこぼしがあれば再送される(届くまで粘る動きになる)
- 混雑が増えると、再送や待ちが増えて遅くなりやすい
UDP(投げっぱなし)
UDPは、投げて終わり。届いたかどうかは、基本的に確定できない。
その代わり軽いので、遅延を小さくしやすい。リアルタイム系や探索で出やすい。
- 接続を張らずに送れる(状態が薄い)
- 応答がないことも普通にある(確実に到達を確認できない)
- NATやWi-Fi品質の影響が、時間帯や距離で「遅い/欠ける/途切れる」に出やすい
どうしてUDPでも正常に動くのか(理屈を整理する)
ここは「届いたか確定できないのに動く」理由を整理する。
1) “最新だけ分かれば良い”通信がある
オンラインゲームの位置・視点・状態などは、次の更新がすぐ来る。
1つ欠けても、次の更新で上書きできる。
この場合、再送して遅れるより「今の情報が早く来る」方が価値が高い。
- 欠けても致命傷になりにくいデータをUDPへ乗せる
- 欠けた分は次の更新で回復する設計にする
2) 重要なものだけ“別扱い”にする
ゲームでも、ログインや購入、重要な操作などは欠けると困る。
こういう部分はTCPに乗せるか、UDPでもアプリ側で確認応答・再送を用意する。
- 重要操作:確実性が必要 → TCP、またはアプリ側で確認応答
- 状態更新:遅延が困る → UDP(欠けても次で上書き)
3) “探索”は一斉通知(ブロードキャスト)で回数を稼げる
LAN内探索は「届く相手がいれば返してくれれば十分」。
一斉通知(ブロードキャスト)で投げ、返ってきたものだけ拾う動きが多い。
全員に確実に届ける必要が無いので、UDPが噛み合う。
OSI参照モデル(通信を整理する概念図)
OSI参照モデルは、通信を層に分けて考える“概念上の整理枠”。
ネットワーク機器の中に、そのまま実体として存在するものではない。
ただ、用語や切り分けの会話を整理する助けになる。
- TCP/UDPは トランスポート層(IPの上)
- ポート番号はトランスポート層の概念
アプリで見るTCP/UDP(どう使われるかに着目)
ここは「名前を聞いたことがあるアプリ」で、使われ方のイメージを作る。
個別アプリの設定手順や攻略には踏み込まず、TCP/UDPの使われ方に焦点を当てる。
Slack / Teams(企業ネットワークで通る経路に集まりやすい)
業務ツールは「欠けると困る」通信が多く、TCPが主になりやすい。
加えて、企業ネットワークで通りやすい経路(HTTPS 443/TCP)へ集約されやすい。
- 使われ方
- メッセージや通知を確実に運ぶ(欠けると困る)
- 長時間の接続を維持し、通知を受け取りやすくする
- 見え方
- ブラウザは開けるのにアプリが落ちる、などはプロキシ/宛先制限/SSL検査の影響が混ざりやすい
IP Messenger(LAN内で相手を見つける・呼びかける)
LAN内で相手を探す用途は、一斉通知(ブロードキャスト)と相性が良い。
そのためUDPが絡む構成になりやすい。
- 使われ方
- 一斉通知で呼びかけ、返ってきた相手だけとやり取りする
- 見え方
- 同一セグメントだと見えるが、VLANや設定で見えなくなる
オンラインゲーム(動きの情報は早さが大事で、ログインや購入みたいな重要操作は確実さが大事)
リアルタイム性が強い領域は、UDPが出やすい。
一方で、重要操作はTCPや確認応答が必要になりやすい。
- 使われ方
- 状態更新はUDPで高速に回す(欠けても次で上書きできるデータが中心)
- 重要操作は確実性を確保する(TCPまたは確認応答)
- 見え方
- Wi-Fiやルータの状態で、時間帯や距離で不安定さが出やすい
Windows Update(欠けると困るデータを確実に運ぶ)
更新配布は、欠けたら破綻するデータを扱う。
そのためTCPで確実に運ぶ構成が自然になる。
- 使われ方
- 大きなデータを分割して確実に取得し、検証して適用する
- 見え方
- 回線が混むと遅くなる。途中断でも再開の仕組みが必要になる
有名どころポート一覧(用途で覚える)
ここは「用途→番号→TCP/UDP」をまとめる。番号だけ覚える運用はズレやすい。
| 用途 | ポート | TCP/UDP | ひとこと |
|---|---|---|---|
| HTTP | 80 | TCP | Web(平文) |
| HTTPS | 443 | TCP | Web(暗号)/企業ネットワークで通りやすい |
| DNS | 53 | UDP/TCP | 通常はUDP、応答が大きいとTCPも出る |
| SSH | 22 | TCP | リモートログイン |
| SMTP | 25 | TCP | メール送信(サーバ間) |
| Submission | 587 | TCP | メール送信(クライアント→サーバ) |
| IMAP | 143 / 993 | TCP | メール受信(993は暗号) |
| POP3 | 110 / 995 | TCP | メール受信(995は暗号) |
| FTP(制御) | 21 | TCP | 制御用。データ側で別ポートが増える |
| SFTP | 22 | TCP | SSH上でファイル転送(FTPとは別物) |
| NTP | 123 | UDP | 時刻同期 |
| DHCP | 67/68 | UDP | IP配布(サーバ/クライアント) |
| RDP | 3389 | TCP | リモートデスクトップ |
| SMB | 445 | TCP | ファイル共有(Windows) |
ここで詰まりやすいポイントは2つ。
- 同じ番号でもTCPとUDPは別物(ルールも別になる)
- FTPは制御とデータが分かれ、データ側で別ポートが増える
外部一覧(網羅で当たりたい時)も置いておく。
C#でTCPとUDP通信を試す(ローカルで完結)
ここは、TCPとUDPの違いを“手を動かして”確認するための最小サンプルを置く。
ネットワークの学習が主目的なので、C#側は入門的な書き方をそっと置く程度に留める。
接続先が無いと試せない問題を避けるため、TCPはローカル(127.0.0.1:18080)でサーバとクライアントを用意し、同じPC内だけで往復できる形にする。
UDPも同様に、ローカルで往復確認できるようにする。
最短サンプル:TCP(ローカルEchoで接続→送受信)
ここは「TCPは接続を張ってから送る」を確認できる最小構成。
ローカルPC内だけで完結するので、社内ネットワークや外部環境に左右されにくい。
- サーバ:
127.0.0.1:18080で待ち受け、受け取った文字列をそのまま返す - クライアント:
127.0.0.1:18080へ接続し、文字列を送って返りを表示する
TCPサーバ(Echo)
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
internal static class TcpEchoServer
{
/// <summary>
/// TCPで待ち受けし、受け取った文字列をそのまま返す簡易Echoサーバ。
/// </summary>
/// <remarks>
/// 試す目的:TCPは「接続を張る→送る→返る」という流れになる点を確認する。
/// 接続先:127.0.0.1:18080(ローカルPC内で完結)
/// </remarks>
public static async Task RunAsync(IPAddress listenAddress, int listenPort, CancellationToken ct)
{
// TcpListenerは「TCPで待ち受けする」ためのクラス
var listener = new TcpListener(listenAddress, listenPort);
// Startで待ち受け開始(ここでポートを掴む)
listener.Start();
try
{
while (!ct.IsCancellationRequested)
{
// AcceptTcpClientAsyncで「接続してきたクライアント」を受け取る(TCPは接続が前提)
var client = await listener.AcceptTcpClientAsync(ct).ConfigureAwait(false);
// 接続は複数来るので、1接続=1処理として別タスクで処理する
_ = Task.Run(() => HandleClientAsync(client, ct), ct);
}
}
catch (OperationCanceledException)
{
// 終了要求で抜ける
}
finally
{
// Stopで待ち受け終了(ポート開放)
listener.Stop();
}
}
/// <summary>
/// 1クライアント分の送受信を処理する。
/// </summary>
private static async Task HandleClientAsync(TcpClient client, CancellationToken ct)
{
// usingで接続を確実に閉じる(TCPは接続の後始末が重要)
using (client)
{
// NetworkStreamはTCP接続上のバイト列の読み書き
using var stream = client.GetStream();
// Reader/Writerで文字列として扱う(UTF-8)
using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, bufferSize: 1024, leaveOpen: true);
using var writer = new StreamWriter(stream, Encoding.UTF8, bufferSize: 1024, leaveOpen: true) { AutoFlush = true };
// 1行受け取る(クライアントが改行で区切って送る想定)
var line = await reader.ReadLineAsync(ct).ConfigureAwait(false);
// 受け取った内容をそのまま返す(Echo)
if (line is not null)
{
await writer.WriteLineAsync(line).ConfigureAwait(false);
}
}
}
}
TCPクライアント(接続→送る→返りを読む)
using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
internal static class TcpEchoClient
{
/// <summary>
/// TCPで接続し、1行送って、返ってきた1行を受け取る。
/// </summary>
/// <remarks>
/// 試す目的:TCPは接続を張ってから通信する点、返りが「同じ接続」上で戻る点を確認する。
/// 接続先:127.0.0.1:18080(TcpEchoServerと揃える)
/// </remarks>
public static async Task<string?> RoundTripAsync(string host, int port, string message, int timeoutMs = 3000)
{
using var cts = new CancellationTokenSource(timeoutMs);
// TcpClientは「TCPで接続する」ためのクラス
using var client = new TcpClient();
// ConnectAsyncで接続を張る(ここが成立しないと送れない)
await client.ConnectAsync(host, port, cts.Token).ConfigureAwait(false);
using var stream = client.GetStream();
using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, bufferSize: 1024, leaveOpen: true);
using var writer = new StreamWriter(stream, Encoding.UTF8, bufferSize: 1024, leaveOpen: true) { AutoFlush = true };
// 1行送る(サーバ側はReadLineで受ける)
await writer.WriteLineAsync(message).ConfigureAwait(false);
// 返りを1行読む(サーバ側は同じ内容をWriteLineで返す)
return await reader.ReadLineAsync(cts.Token).ConfigureAwait(false);
}
}
最短サンプル:UDP(ローカルで送る→返す→一致確認)
ここは「UDPは投げっぱなしで、到達の確定が難しい」を前提に、往復で確かめる最小構成。
UDPは“応答が無い構成”も多いので、疎通確認は返す側を用意して往復で確定する。
- サーバ:
127.0.0.1:18081で待ち受け、受け取ったバイト列をそのまま返す - クライアント:
127.0.0.1:18081へ送って、同一内容が返るかを見る
UDPサーバ(Echo)
using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
internal static class UdpEchoServer
{
/// <summary>
/// UDPで受けたデータを、そのまま送り返す簡易Echoサーバ。
/// </summary>
/// <remarks>
/// 試す目的:UDPは接続を張らずに送れる一方、到達の確定が難しい点を「往復」で確認する。
/// 待受:127.0.0.1:18081(ローカルPC内で完結)
/// </remarks>
public static async Task RunAsync(int listenPort, CancellationToken ct)
{
if (listenPort < 1 || listenPort > 65535) throw new ArgumentOutOfRangeException(nameof(listenPort));
// UdpClientはUDP送受信のためのクラス
using var udp = new UdpClient(listenPort);
while (!ct.IsCancellationRequested)
{
UdpReceiveResult recv;
try
{
// ReceiveAsyncで「1つのデータグラム」を受け取る(UDPはメッセージ単位)
recv = await udp.ReceiveAsync(ct).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
break;
}
// 受け取った内容を、そのまま送り返す(Echo)
await udp.SendAsync(recv.Buffer, recv.Buffer.Length, recv.RemoteEndPoint).ConfigureAwait(false);
}
}
}
UDPクライアント(送る→返りを待つ→一致確認)
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
internal static class UdpEchoClient
{
/// <summary>
/// UDPで送信し、指定時間内に同一内容の応答が返るかを確認する。
/// </summary>
/// <remarks>
/// 試す目的:UDPは送った側で到達を確定できない点を、Echoの往復で確認する。
/// 接続先:127.0.0.1:18081(UdpEchoServerと揃える)
/// </remarks>
public static async Task<bool> RoundTripAsync(string host, int port, string message, int timeoutMs = 2000)
{
if (string.IsNullOrWhiteSpace(host)) throw new ArgumentException("host is empty.", nameof(host));
if (port < 1 || port > 65535) throw new ArgumentOutOfRangeException(nameof(port));
if (timeoutMs < 1) throw new ArgumentOutOfRangeException(nameof(timeoutMs));
var payload = Encoding.UTF8.GetBytes(message ?? string.Empty);
using var udp = new UdpClient();
// Connectは「宛先を既定にする」だけで、TCPの接続確立とは別物
udp.Connect(host, port);
// 1つのデータグラムとして送る
await udp.SendAsync(payload, payload.Length).ConfigureAwait(false);
using var cts = new CancellationTokenSource(timeoutMs);
try
{
// 返りを待つ(返らない場合も普通にあり得るのでタイムアウトを持つ)
var recv = await udp.ReceiveAsync(cts.Token).ConfigureAwait(false);
// 返ってきた内容が一致するかを確認
var echoed = Encoding.UTF8.GetString(recv.Buffer);
return string.Equals(echoed, message ?? string.Empty, StringComparison.Ordinal);
}
catch (OperationCanceledException)
{
return false;
}
catch (SocketException)
{
return false;
}
}
}
TCP/UDPで起きやすい困りごと(起き方→対策)
ここは、TCP/UDPを選んだ後に長引きやすいポイントを短くまとめる。
1) ポート番号は合っているのに、TCP/UDPの違いで通らない
- 起き方
「443を開けた」で話が進み、実際は TCP 443 だけ通って UDP 443 が落ちている(または逆) - 対策
ポート番号に加えて TCP か UDP かまで含めて確認する(設定とログも同じ観点で見る) - 目印
同じポート番号なのに、片方だけ通る/片方だけ落ちる
2) UDPの無応答を、そのまま不通と決める
- 起き方
送ったが返らない → すぐ不通と判断して切り分けが止まる(応答が無い設計もある) - 対策
返す側を用意して往復で確かめる(Echoのような最小構成で確認する) - 目印
仕様として「返さない」構成が含まれている
3) NATの状態が消えて、しばらくすると途切れる
- 起き方
最初は動くが、放置後に途切れる/再接続が必要になる - 対策
時間経過を含めて観測する。必要なら KeepAlive や再送の設計を検討する - 目印
起動直後は正常でも、時間が経つと不調になることがある
4) 回線や経路が混み合い、遅くなる
- 起き方
夕方などで遅くなる。再送が増え、待ちが積み上がる - 対策
タイムアウト、再試行、サイズ、同時接続数を見直す。まず状況をログで残す - 目印
時間帯で遅延が変わる/大きいデータで顕著になる
チェックリスト(レビューで見る所)
ここは、設計と実装の確認観点をまとめる。
| 観点 | 見るポイント | よくあるズレ |
|---|---|---|
| 選択理由 | 欠けると困るか、遅延が困るかが言語化されているか | “なんとなくUDP” “なんとなくTCP” |
| ルール | ポート番号にTCP/UDPの別が付いているか | 番号だけで会話してズレる |
| タイムアウト | TCP接続/読み書き、UDP受信待ちが明示されているか | 待ち続けて固まる |
| 再試行 | 再送が必要な所に方針があるか(冪等も含む) | 二重送信で整合が崩れる |
| ログ | どこで止まったか(名前解決/接続/受信待ち)が残るか | “繋がらない” だけが残る |
| NAT | 放置後の挙動が観測できるか | “最初だけOK” を見逃す |
セルフチェック(5問)
- TCPとUDPの違いを「欠けたら埋める」「投げっぱなし」で説明できるか
- ソケットを「IPアドレス+ポート番号+TCP/UDPの別」で説明できるか(例つきで言えるか)
- 同じポート番号でもTCPとUDPが別物だと説明できるか
- UDPが届いたか確定できないのに動く理由を、更新の上書き・重要操作の別扱いで説明できるか
- NATが時間経過で効いてくる理由を説明できるか
回答の目安
- 1: TCPは欠けた分を再送で埋める。UDPは投げっぱなしで到達の確定が難しい
- 2: 例は
TCP 172.16.1.1:8080/UDP 172.16.1.1:8080。数字が同じでも別物になる - 3: ルールや設定は「番号+TCP/UDP」で別。番号だけだとズレる
- 4: 状態更新は次の更新で上書きできる。重要操作はTCPか確認応答で確実性を確保する
- 5: NATは変換の状態が永続ではない。放置後に途切れる問題が出ることがある
関連トピック
連載Index(読む順・公開済リンクはここが最新): S00: 門前の誓い(総合Index)