結論

ソケットとは、プログラムが通信するための出入口で、IPアドレスとポート番号の組で決まります。中身の運び方には、確実に届ける TCP と、速く送る UDP があります。

この記事でわかること

  • TCPとUDPの違い表と、用途別の使い分け8例
  • Pythonの通信とtcpdumpで見た12パケット対4パケット
  • エラー原文10種(Address already in use等)と直し方

2026年10月22日更新|Python 3.11.15・tcpdump 4.99.4で実行確認

アプリはソケットにデータを渡すだけで、TCP か UDP かの運び方は OS が受け持ちます。相手側も、同じようにソケットからデータを受け取ります。

flowchart TD
    A[アプリ A<br>Python・Java など] --> SA[ソケット<br>10.0.0.1:50000]
    SA --> OS[OS の TCP または UDP]
    OS --> N[ネットワーク]
    N --> SB[ソケット<br>10.0.0.2:8080]
    SB --> B[アプリ B<br>サーバー]

急いでいる人向けに、よく調べられることの答えを先にまとめます。

知りたいこと 答え 詳しい節
ソケットとは 通信の出入口。IPアドレス+ポート番号で決まる ソケットとは
TCPとUDPの違い TCP は接続・順番・再送あり、UDP は無し 違い
使い分け 欠けたら困るなら TCP、遅れたら困るなら UDP 使い分け
3ウェイハンドシェイク SYN → SYN+ACK → ACK の3回で接続 ハンドシェイク
Address already in use 同じポートを使うプロセスが残っている エラー
Connection refused 相手のポートで誰も待ち受けていない エラー
timed out 相手から応答が返ってこない エラー
WebSocketとの違い WebSocket は TCP ソケットの上の決まりごと WebSocket

確認環境:Ubuntu 24.04(Linux カーネル 6.18)・Python 3.11.15。TCP と UDP のサーバー・クライアントは Python の socket モジュールで書いて 127.0.0.1 で通信し、ソケットの状態を ss(iproute2 6.1.0)、パケットを tcpdump 4.99.4、システムコールを strace で確かめました。応答が返らない相手・UDP の取りこぼしは、ip netns で分けたネットワークの中で再現しました。ほかに OpenJDK 21.0.10・Node.js 22.22.2・curl 8.5.0・OpenBSD netcat 1.226・websockets 17.3(Python)で同じサーバーにつないでいます。TCP・UDP・WebSocket の仕様は RFC 9293・RFC 768・RFC 6455、Linux の既定値はカーネルのドキュメントとソース、Windows のエラーは Microsoft Learn で確かめました(確認日 2026年10月22日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「ソケットとは|TCPとUDPの違いをPythonの通信とtcpdumpで解説」

ソケットとは(意味・IPアドレスとポート番号)

この節では、「ソケット」という言葉が通信の世界で何を指しているのかが分かります。

ソケット(socket)は、プログラムがネットワークでデータを送ったり受けたりするための出入口です。 英語の socket は「差し込み口」という意味で、電源のコンセントのように、プログラムを通信路に差し込む口だと考えると分かりやすくなります。ソケットを使った通信を「ソケット通信」、ソケットを使ってプログラムを書くことを「ソケットプログラミング」と呼びます。

ソケットは次の値の組で決まります。

1IPアドレス

どのコンピューターか。建物の住所にあたる。例:127.0.0.1・192.168.1.10

2ポート番号

そのコンピューターのどのプログラムか。部屋番号にあたる。0〜65535 の数字

3プロトコル

運び方。確実に届ける TCP か、速く送る UDP か。同じ番号でも TCP と UDP は別のポート

「IPアドレス:ポート番号」の書き方(例 127.0.0.1:34901)は、ソケットのアドレスを表しています。TCP の1本の接続は「自分の IP・自分のポート・相手の IP・相手のポート・プロトコル」の5つの値の組で見分けられます。だから、1つのサーバーのポート(例 443)に、何千ものクライアントが同時につながれます。

ソケットは OS が渡す「ファイル」の1つ

プログラムから見ると、ソケットは OS が渡してくれる番号(ファイルディスクリプタ)です。Linux では、ファイルと同じように番号で読み書きします。後で作る Python の TCP サーバーが動いている間に、そのプロセスが開いているものを見ると、3番と4番がソケットでした。

$ ls -l /proc/27113/fd
0 -> /dev/null
3 -> socket:[153642]
4 -> socket:[153651]

(1番と2番は出力先のログファイルなので省いています。)3番は待ち受け用のソケット、4番はクライアントとの1本の接続用のソケットです。ss -e で見た接続の ino:153651 と番号が一致しました。

$ ss -tane state established "sport = :34901"
Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
0      0          127.0.0.1:34901    127.0.0.1:39196 ino:153651 sk:12 cgroup:/ <->

「ソケット」の別の意味との見分け

使われる場面 意味 この記事の対象か
ネットワーク・プログラミング 通信の出入口(BSD ソケット・Winsock) 対象
同じマシンの中の通信 UNIX ドメインソケット(ファイルのパスで指定) FAQ で扱う
CPU・電子部品 チップを差し込む台座(CPU ソケット) 対象外
電気 コンセント・電球の差し込み口 対象外

ソケット通信の仕組み(socket・bind・listen・accept・connect)

この節では、サーバーとクライアントがどの順番で OS に頼んで通信を始めるのかが分かります。

ソケット通信は、どの言語でも OS の同じ機能(システムコール)を同じ順番で呼びます。この形は BSD UNIX で作られたので「BSD ソケット」と呼ばれ、Windows の Winsock もほぼ同じ形です。

flowchart TD
    A[サーバー<br>socket・bind・listen] --> B[クライアント<br>socket・connect]
    B --> C[3ウェイハンドシェイク]
    C --> D[サーバー accept<br>接続用のソケットが返る]
    D --> E[send と recv]
    E --> F[close で終わる]
1

socket:出入口を作る

TCP なら SOCK_STREAM、UDP なら SOCK_DGRAM を指定する。OS から番号が返る。

2

bind:住所と部屋番号を決める(サーバー)

どの IP アドレスのどのポートで受けるかを OS に登録する。ここで Address already in use が出る。

3

listen:受付を始める(TCP のサーバー)

接続を受けられる状態(LISTEN)にする。まだ accept していない接続を何本まで溜めるかを指定する。

4

connect:相手につなぐ(クライアント)

TCP ではここで3ウェイハンドシェイクが行われる。Connection refused・timed out はここで出る。

5

accept:つながった相手を受け取る(サーバー)

接続1本ごとに新しいソケットが返る。待ち受け用のソケットはそのまま残る。

6

send・recv:やり取りして close で閉じる

recv が空のデータを返したら、相手が接続を閉じた合図。

strace で見た実際のシステムコール

後の節の Python の TCP サーバーとクライアントを strace の下で動かし、ネットワークに関わる呼び出しだけを取り出しました。サーバー側です。

socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_IP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(34903), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
listen(3, 128)                    = 0
accept4(3, {sa_family=AF_INET, sin_port=htons(47282), sin_addr=inet_addr("127.0.0.1")}, [16], SOCK_CLOEXEC) = 4
recvfrom(4, "\343\201\223\343\202\223\343\201\253\343\201\241\343\201\257", 1024, 0, NULL, NULL) = 15
sendto(4, "ECHO: \343\201\223\343\202\223\343\201\253\343\201\241\343\201\257", 21, 0, NULL, 0) = 21
recvfrom(4, "TCP", 1024, 0, NULL, NULL) = 3
sendto(4, "ECHO: TCP", 9, 0, NULL, 0) = 9
recvfrom(4, "", 1024, 0, NULL, NULL) = 0
accept4(3,

クライアント側です。

socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_TCP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(34903), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress)
sendto(3, "\343\201\223\343\202\223\343\201\253\343\201\241\343\201\257", 15, 0, NULL, 0) = 15
recvfrom(3, "ECHO: \343\201\223\343\202\223\343\201\253\343\201\241\343\201\257", 1024, 0, NULL, NULL) = 21
sendto(3, "TCP", 3, 0, NULL, 0)         = 3
recvfrom(3, "ECHO: TCP", 1024, 0, NULL, NULL) = 9

読み取れることです。

  • accept4 は待ち受け用の3番とは別の4番を返し、以後のやり取りは4番で行われています。 待ち受け用のソケットは、次の接続を受けるために残ります。
  • 「こんにちは」は UTF-8 で15バイトです。recvfrom(4, "", ...) = 0 は、相手が閉じたことを表す長さ0の受信です。
  • クライアントの connect が EINPROGRESS を返しているのは、タイムアウト(5秒)を指定したので Python がノンブロッキングで接続を始め、完了を待つ形にしているためです。
  • listen(3, 128) の 128 は、Python が listen() の引数を省いたときに選んだ値です。

TCPとUDPの違い(比較表)

この節では、TCP と UDP が何を引き受け、何を引き受けないのかが表で分かります。

TCP(Transmission Control Protocol)と UDP(User Datagram Protocol)は、どちらも OSI 参照モデルの第4層(トランスポート層)のプロトコルで、IP の上で動きます。層の全体像は、関連記事「OSI参照モデルとは|7層の早見表とTCP/IP対応・パケット実例」で扱っています。

TCP と UDP のいちばん大きな違い

TCP先に接続してから送る。欠けたら送り直し、順番を並べ直す。相手の受け取る速さに合わせて待つ
→
UDP接続せずにいきなり送る。欠けても送り直さない。届く・届かない・順番の確認はアプリの仕事

TCP は「書留の連続した荷物」、UDP は「1枚ずつ投げるはがき」

項目 TCP UDP
接続 あり(3ウェイハンドシェイク) なし
届いたかの確認・再送 あり(ACK と再送) なし
順番 並べ直して渡す 届いた順のまま
データの区切り 無い(ひと続きのバイト列) ある(1回送った分=1個)
速さを相手に合わせる あり(フロー制御・輻輳制御) なし
ヘッダーの大きさ 20バイト〜(オプションで増える) 8バイト
1対多の送信 できない ブロードキャスト・マルチキャストができる
ソケットの種類 SOCK_STREAM SOCK_DGRAM
主な用途 Web(HTTP/1.1・2)・メール・SSH・DB DNS・動画や音声の通話・ゲーム・HTTP/3(QUIC)

TCP の仕様は RFC 9293(2022年に RFC 793 を置き換えたもの)、UDP は RFC 768 です。UDP のヘッダーは送信元ポート・宛先ポート・長さ・チェックサムの4つだけで、合わせて8バイトです。

同じやり取りを TCP と UDP で行い、tcpdump でパケットの数を数えました(後の節で中身を見ます)。

12個TCP のパケット接続3・データと確認6・切断3
4個UDP のパケット送信2・返信2だけ
9,644個UDP で失われた数1万個を受け手より速く送った例

TCPとUDPの使い分け(判断表)

やりたいこと 選ぶもの 理由
Web ページ・API(HTTP/1.1・HTTP/2) TCP 1バイトでも欠けると壊れる
ファイル転送・メール・SSH・DB の接続 TCP 全部が順番どおりに届く必要がある
名前解決(DNS) UDP(大きい応答は TCP) 1往復で済む小さな問い合わせ
時刻合わせ(NTP)・DHCP UDP 小さく、送り直すより次を送る
ビデオ通話・音声通話・ライブ配信 UDP 遅れて届いた音声は使えない
オンラインゲームの位置情報 UDP 最新の位置だけが要る
ログ・メトリクスの送信(syslog・StatsD) UDP が多い 欠けても困らず、送る側を待たせない
HTTP/3 UDP(QUIC) 再送や順番は QUIC が UDP の上で行う
迷ったら TCP 届く・順番・速さの調整を OS が引き受ける

「欠けたら困る」なら TCP、「遅れたら困る」なら UDP が基本の考え方です。HTTP/3 の QUIC(RFC 9000)のように、UDP の上に自分で再送や暗号化を作るプロトコルもあります。DNS は UDP の53番が基本で、応答が大きいときなどは TCP の53番も使います(RFC 7766)。

TCPの3ウェイハンドシェイクと切断(tcpdumpで見る)

この節では、TCP が接続を始めるときと終えるときに、実際にどんなパケットが流れるのかが分かります。

3ウェイハンドシェイクは、TCP が送る前に「送ります」「どうぞ、こちらも送ります」「了解」の3回のやり取りで接続を作る手順です。 お互いの最初の番号(シーケンス番号)をここで伝え合い、以後のデータは番号で順番と抜けを確かめます。

sequenceDiagram
    participant C as クライアント
    participant S as サーバー
    C->>S: SYN seq=x
    S->>C: SYN+ACK seq=y ack=x+1
    C->>S: ACK ack=y+1
    Note over C,S: ESTABLISHED
    C->>S: データ
    S->>C: ACK

後の節の TCP のクライアントが「こんにちは」と「TCP」を送って閉じるまでを、tcpdump -i lo -nn -S 'tcp port 34901' で取った全部のパケットです。

22:27:33.835270 IP 127.0.0.1.38684 > 127.0.0.1.34901: Flags [S], seq 3190285525, win 65495, options [mss 65495,sackOK,TS val 4050114198 ecr 0,nop,wscale 10], length 0
22:27:33.835285 IP 127.0.0.1.34901 > 127.0.0.1.38684: Flags [S.], seq 3333960366, ack 3190285526, win 65483, options [mss 65495,sackOK,TS val 1379117592 ecr 4050114198,nop,wscale 10], length 0
22:27:33.835297 IP 127.0.0.1.38684 > 127.0.0.1.34901: Flags [.], ack 3333960367, win 64, options [nop,nop,TS val 4050114198 ecr 1379117592], length 0
22:27:33.835357 IP 127.0.0.1.38684 > 127.0.0.1.34901: Flags [P.], seq 3190285526:3190285541, ack 3333960367, win 64, options [nop,nop,TS val 4050114198 ecr 1379117592], length 15
22:27:33.835360 IP 127.0.0.1.34901 > 127.0.0.1.38684: Flags [.], ack 3190285541, win 64, options [nop,nop,TS val 1379117592 ecr 4050114198], length 0
22:27:33.835451 IP 127.0.0.1.34901 > 127.0.0.1.38684: Flags [P.], seq 3333960367:3333960388, ack 3190285541, win 64, options [nop,nop,TS val 1379117592 ecr 4050114198], length 21
22:27:33.835454 IP 127.0.0.1.38684 > 127.0.0.1.34901: Flags [.], ack 3333960388, win 64, options [nop,nop,TS val 4050114198 ecr 1379117592], length 0
22:27:33.835468 IP 127.0.0.1.38684 > 127.0.0.1.34901: Flags [P.], seq 3190285541:3190285544, ack 3333960388, win 64, options [nop,nop,TS val 4050114198 ecr 1379117592], length 3
22:27:33.835487 IP 127.0.0.1.34901 > 127.0.0.1.38684: Flags [P.], seq 3333960388:3333960397, ack 3190285544, win 64, options [nop,nop,TS val 1379117592 ecr 4050114198], length 9
22:27:33.835532 IP 127.0.0.1.38684 > 127.0.0.1.34901: Flags [F.], seq 3190285544, ack 3333960397, win 64, options [nop,nop,TS val 4050114198 ecr 1379117592], length 0
22:27:33.840938 IP 127.0.0.1.34901 > 127.0.0.1.38684: Flags [F.], seq 3333960397, ack 3190285545, win 64, options [nop,nop,TS val 1379117597 ecr 4050114198], length 0
22:27:33.840956 IP 127.0.0.1.38684 > 127.0.0.1.34901: Flags [.], ack 3333960398, win 64, options [nop,nop,TS val 4050114203 ecr 1379117597], length 0
行 Flags 意味
1〜3 S → S. → . 3ウェイハンドシェイク(SYN・SYN+ACK・ACK)
4 P. length 15 「こんにちは」15バイト。seq は 3190285526 から
5 . ack 3190285541 「3190285540 番まで受け取った」という確認
6〜9 P. と . 返信21バイト、「TCP」3バイト、返信9バイト
10〜12 F. → F. → . 切断。お互いが FIN を送り、最後に ACK

SYN の seq 3190285525 に対して、相手の ack が 3190285526(+1)になっているのが「受け取った」の印です。 データを送ると、seq は送ったバイト数だけ進みます(3190285526 + 15 = 3190285541)。Flags の記号は S が SYN、F が FIN、P が PUSH、R が RST、. が ACK です。tcpdump の読み方とフィルタ式は、関連記事「tcpdumpの使い方|フィルタ式一覧と出力の読み方」で扱っています。

切断と TIME-WAIT

TCP の切断は、お互いが FIN を送り、それぞれ ACK を返す形です(上の例では、相手の ACK と FIN が1つのパケット F. にまとまったので3パケットでした)。先に閉じた側は、最後の ACK のあと TIME-WAIT という状態でしばらく残ります。 遅れて届いたパケットが、次の接続に紛れ込まないようにするためです。

$ ss -tano state time-wait "dport = :34901"
Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
0      0          127.0.0.1:38684    127.0.0.1:34901 timer:(timewait,43sec,0)
0      0          127.0.0.1:39196    127.0.0.1:34901 timer:(timewait,59sec,0)

RFC 9293 は TIME-WAIT の長さを 2×MSL(MSL はこの仕様では2分)としていますが、Linux はカーネルのソースで TCP_TIMEWAIT_LEN を約60秒に固定しています。上の timer:(timewait,59sec,0) は、残り59秒という意味です。この TIME-WAIT が、後の節の「Address already in use」の原因の1つになります。

TCP の状態の一覧

状態 どちら側 意味
LISTEN サーバー 接続を待ち受けている
SYN-SENT クライアント SYN を送り、返事を待っている
SYN-RECEIVED サーバー SYN を受けて SYN+ACK を返した
ESTABLISHED 両方 接続ができ、データを送れる
FIN-WAIT-1・2 先に閉じた側 FIN を送り、相手の FIN を待っている
CLOSE-WAIT 後から閉じる側 相手の FIN を受け、自分の close を待っている
LAST-ACK 後から閉じる側 FIN を送り、最後の ACK を待っている
TIME-WAIT 先に閉じた側 終わったあと、しばらく残る

ss では FIN-WAIT-1 のように表示されます。CLOSE-WAIT が溜まり続けるときは、アプリが受け取った接続を close していない(閉じ忘れ)ことが多い、というのが読み方の目安です。

PythonでTCPのサーバーとクライアントを作る

この節では、そのまま動く TCP のサーバーとクライアントのコードと、通信したときの出力・ss の見え方が分かります。

送られた文字に「ECHO: 」を付けて返すサーバーです。

import socket

HOST, PORT = "127.0.0.1", 34901

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as srv:
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind((HOST, PORT))
    srv.listen()
    print(f"待ち受け開始 {HOST}:{PORT}", flush=True)
    while True:
        conn, addr = srv.accept()          # 接続ごとに新しいソケットが返る
        with conn:
            print(f"接続を受けた 相手={addr}", flush=True)
            while True:
                data = conn.recv(1024)
                if not data:               # 空の bytes = 相手が閉じた
                    print("相手が切断した", flush=True)
                    break
                print(f"受信 {len(data)}バイト: {data!r}", flush=True)
                conn.sendall(b"ECHO: " + data)

クライアントです。socket.create_connection は、ソケットを作って connect するまでをまとめて行う関数です。

import socket

HOST, PORT = "127.0.0.1", 34901

with socket.create_connection((HOST, PORT), timeout=5) as s:
    print(f"接続した 自分={s.getsockname()} 相手={s.getpeername()}")
    for msg in ["こんにちは", "TCP"]:
        s.sendall(msg.encode())
        print("返信:", s.recv(1024).decode())

サーバーを起動したあと、待ち受けているかを ss で確かめ、クライアントを動かしました。

$ ss -tlnp "sport = :34901"
State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess
LISTEN 0      128        127.0.0.1:34901      0.0.0.0:*    users:(("python3",pid=27113,fd=3))

$ python3 tcp_client.py
接続した 自分=('127.0.0.1', 38684) 相手=('127.0.0.1', 34901)
返信: ECHO: こんにちは
返信: ECHO: TCP

サーバー側の出力です。

待ち受け開始 127.0.0.1:34901
接続を受けた 相手=('127.0.0.1', 38684)
受信 15バイト: b'\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf'
受信 3バイト: b'TCP'
相手が切断した

クライアントのポート 38684 は、こちらが指定していないのに OS が自動で選んだ番号(エフェメラルポート)です。 LISTEN の行の Send-Q 128 は、まだ accept されていない接続を溜められる数(backlog)を表しています。

接続している間に ss を見ると、同じ接続が両側から2行に見えます。

$ ss -tanpo state established "( sport = :34901 or dport = :34901 )"
Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
0      0          127.0.0.1:34901    127.0.0.1:39196 users:(("python3",pid=27113,fd=4))
0      0          127.0.0.1:39196    127.0.0.1:34901 users:(("python3",pid=27192,fd=3))
ss のオプション 意味
-t・-u TCP だけ・UDP だけ
-l 待ち受け中(LISTEN)だけ
-a 待ち受けと接続の両方
-n ポートを名前(http など)にせず数字で出す
-p 使っているプロセス(root でないと他人の分は出ない)
-o・-e タイマー(TIME-WAIT の残り時間など)・inode
state established 状態で絞る

sendall は全部送り終わるまで繰り返し送る関数です。Python のドキュメントによると、send は送れたバイト数を返すだけで、残りを送るのはアプリの責任です。TCP では、基本は sendall を使います。

PythonでUDPのサーバーとクライアントを作る

この節では、UDP のサーバーとクライアントのコードと、TCP との出力の違いが分かります。

UDP は接続しないので、listen も accept もありません。recvfrom で「データと送り主」を受け取り、sendto で送り主に返します。

import socket

HOST, PORT = "127.0.0.1", 34902

with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as srv:
    srv.bind((HOST, PORT))
    print(f"待ち受け開始 {HOST}:{PORT}", flush=True)
    while True:
        data, addr = srv.recvfrom(65535)   # 1回で1つのデータグラム
        print(f"受信 {len(data)}バイト 相手={addr}: {data!r}", flush=True)
        srv.sendto(b"ECHO: " + data, addr)
import socket

HOST, PORT = "127.0.0.1", 34902

with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
    s.settimeout(2)
    for msg in ["こんにちは", "UDP"]:
        s.sendto(msg.encode(), (HOST, PORT))
        data, addr = s.recvfrom(65535)
        print(f"返信 {addr}: {data.decode()}  自分={s.getsockname()}")
$ ss -ulnp "sport = :34902"
State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess
UNCONN 0      0          127.0.0.1:34902      0.0.0.0:*    users:(("python3",pid=27337,fd=3))

$ python3 udp_client.py
返信 ('127.0.0.1', 34902): ECHO: こんにちは  自分=('0.0.0.0', 60660)
返信 ('127.0.0.1', 34902): ECHO: UDP  自分=('0.0.0.0', 60660)

UDP のソケットの状態は、接続が無いので LISTEN ではなく UNCONN と表示されます。クライアントは bind していませんが、最初の sendto のときに OS がポート 60660 を割り当てました。

同じやり取りの tcpdump('udp port 34902')です。

22:28:06.515815 IP 127.0.0.1.60660 > 127.0.0.1.34902: UDP, length 15
22:28:06.515915 IP 127.0.0.1.34902 > 127.0.0.1.60660: UDP, length 21
22:28:06.515956 IP 127.0.0.1.60660 > 127.0.0.1.34902: UDP, length 3
22:28:06.515976 IP 127.0.0.1.34902 > 127.0.0.1.60660: UDP, length 9

TCP では12個だったパケットが、UDP では送信と返信の4個だけです。 ハンドシェイク・確認(ACK)・切断がありません。そのぶん、届かなかったときに気づく仕組みもありません。クライアントに settimeout(2) を付けているのは、返事が来ないときに recvfrom が永遠に待ち続けないようにするためです。

TCPはバイト列、UDPはデータグラム(区切りの違い)

この節では、ソケットプログラミングでいちばんつまずきやすい「メッセージの区切り」の違いと、その解決方法が分かります。

同じ3つのデータを TCP と UDP で送り、受け手がどう受け取るかを確かめました。

import socket, time

# TCP:3回に分けて送っても、受け手から見ると区切りのないバイト列
srv = socket.create_server(("127.0.0.1", 34906))
c = socket.create_connection(("127.0.0.1", 34906)); s, _ = srv.accept()
for msg in [b"AAA", b"BBBB", b"CC"]:
    c.sendall(msg)
time.sleep(0.1)
print("TCP  送信: [b'AAA', b'BBBB', b'CC']  → recv(1024) 1回目:", s.recv(1024))

# UDP:送った回数・大きさのまま届く(1回の recvfrom で1つ)
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); u.bind(("127.0.0.1", 34907))
v = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for msg in [b"AAA", b"BBBB", b"CC"]:
    v.sendto(msg, ("127.0.0.1", 34907))
time.sleep(0.1)
print("UDP  送信: [b'AAA', b'BBBB', b'CC']  → recvfrom を3回:", [u.recvfrom(1024)[0] for _ in range(3)])

# UDP:受け取る側のバッファが小さいと、はみ出た分は捨てられる
u.sendto(b"X" * 100, u.getsockname())
print("UDP  100バイトを recvfrom(10) で受けると:", u.recvfrom(10)[0], "(残り90バイトは捨てられる)")

# UDP:1つのデータグラムの上限(IPv4 は 65,507 バイト)
for n in (65507, 65508):
    try:
        v.sendto(b"x" * n, ("127.0.0.1", 34907)); print(f"UDP  {n}バイトを送信: 成功")
    except OSError as e:
        print(f"UDP  {n}バイトを送信: {e!r}")
TCP  送信: [b'AAA', b'BBBB', b'CC']  → recv(1024) 1回目: b'AAABBBBCC'
UDP  送信: [b'AAA', b'BBBB', b'CC']  → recvfrom を3回: [b'AAA', b'BBBB', b'CC']
UDP  100バイトを recvfrom(10) で受けると: b'XXXXXXXXXX' (残り90バイトは捨てられる)
UDP  65507バイトを送信: 成功
UDP  65508バイトを送信: OSError(90, 'Message too long')

TCP(ストリーム)

3回送っても1回の recv で b'AAABBBBCC' と届いた

逆に、1回で送ったものが2回に分かれて届くこともある

区切りはアプリが決める(長さを先頭に付ける・改行で区切る)

UDP(データグラム)

送った3個がそのまま3個で届いた

受け取るバッファより大きい分は捨てられる

1個の上限は IPv4 で 65,507 バイト。超えると Message too long

65,507 は「IP パケットの最大 65,535 − IP ヘッダー 20 − UDP ヘッダー 8」です。実際のネットワークでは、1,500 バイト前後(MTU)を超える UDP は IP のレベルで分割され、1片でも失われると全体が失われるので、アプリは小さめの大きさで送るのが一般的です。

TCP でメッセージを区切る(長さ付き・複数クライアント対応の完成コード)

TCP でメッセージを1つずつ受け取るには、先頭に「本体の長さ(4バイト)」を付けて送り、受け手は長さの分だけ読み切る方法がよく使われます。スレッドで複数のクライアントを同時に受け、無言のクライアントは30秒で切るサーバーです。

"""複数のクライアントを同時に受け、メッセージの区切りを「4バイトの長さ+本体」で付けるTCPサーバー"""
import socket, struct, threading

HOST, PORT = "127.0.0.1", 34916

def recv_exact(sock, n):
    buf = b""
    while len(buf) < n:
        chunk = sock.recv(n - len(buf))
        if not chunk:
            raise ConnectionError("相手が切断した")
        buf += chunk
    return buf

def recv_msg(sock):
    (length,) = struct.unpack("!I", recv_exact(sock, 4))   # ! = ネットワークバイトオーダー
    return recv_exact(sock, length)

def send_msg(sock, data):
    sock.sendall(struct.pack("!I", len(data)) + data)

def handle(conn, addr):
    with conn:
        conn.settimeout(30)                     # 無言のクライアントを30秒で切る
        try:
            while True:
                msg = recv_msg(conn)
                send_msg(conn, msg.upper())
        except (ConnectionError, TimeoutError) as e:
            print(f"{addr} 終了: {e!r}", flush=True)

if __name__ == "__main__":
    with socket.create_server((HOST, PORT)) as srv:  # SO_REUSEADDR は POSIX で自動で付く
        print(f"待ち受け開始 {HOST}:{PORT}", flush=True)
        while True:
            conn, addr = srv.accept()
            threading.Thread(target=handle, args=(conn, addr), daemon=True).start()

3つのクライアントを同時に動かすコードです(サーバーのファイル名を framed_server.py として、同じ関数を読み込みます)。

import socket, threading
from framed_server import recv_msg, send_msg   # 同じ関数を使う

def client(name):
    with socket.create_connection(("127.0.0.1", 34916), timeout=5) as s:
        for word in ["alpha", "beta"]:
            send_msg(s, f"{name}:{word}".encode())
            print(name, "→", recv_msg(s).decode())

ts = [threading.Thread(target=client, args=(f"c{i}",)) for i in range(1, 4)]
for t in ts: t.start()
for t in ts: t.join()
c1 → C1:ALPHA
c2 → C2:ALPHA
c1 → C1:BETA
c2 → C2:BETA
c3 → C3:ALPHA
c3 → C3:BETA

サーバー側の出力です。

待ち受け開始 127.0.0.1:34916
('127.0.0.1', 45902) 終了: ConnectionError('相手が切断した')
('127.0.0.1', 45912) 終了: ConnectionError('相手が切断した')
('127.0.0.1', 45918) 終了: ConnectionError('相手が切断した')

3本の接続が同時に処理され、返事の順番はスレッドの進み方で入れ替わりました。それぞれのクライアントの中では、送った順に返事が届いています。struct の !I は「ネットワークバイトオーダー(ビッグエンディアン)の4バイトの符号なし整数」で、別の言語で受け手を書いても同じ形で読めます。

UDPは届かないことがある(取りこぼしの実測)

この節では、UDP では本当にデータが消えること、TCP ではなぜ消えないのかが数値で分かります。

ip netns で分けたネットワークの中で、1,000 バイトのデータを1万回、受け手の処理より速く送りました。受け手の受信バッファは 64KB にしています。比べるために、同じ量を TCP でも送りました。

import socket, threading, time

N, SIZE = 10000, 1000          # 1,000バイトを1万回 = 10MB

def udp_test():
    r = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    r.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 65536)   # 受信バッファを小さめに
    r.bind(("127.0.0.1", 34912)); r.settimeout(1)
    got = []
    def recv():
        try:
            while True:
                got.append(r.recv(2048)); time.sleep(0.00005)   # 受け手の処理が少し遅い
        except TimeoutError:
            pass
    t = threading.Thread(target=recv); t.start()
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    for i in range(N):
        s.sendto(i.to_bytes(4, "big") + b"x" * (SIZE - 4), ("127.0.0.1", 34912))
    t.join()
    seq = [int.from_bytes(d[:4], "big") for d in got]
    print(f"UDP: 送信 {N} 個 → 受信 {len(got)} 個(失われた {N-len(got)} 個)  順番の入れ替わり {sum(1 for a,b in zip(seq, seq[1:]) if b < a)} 回")

def tcp_test():
    srv = socket.create_server(("127.0.0.1", 34913))
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 65536)
    total = 0
    def recv():
        nonlocal total
        c, _ = srv.accept()
        while (d := c.recv(2048)):
            total += len(d); time.sleep(0.00005)
    t = threading.Thread(target=recv); t.start()
    s = socket.create_connection(("127.0.0.1", 34913))
    for i in range(N):
        s.sendall(i.to_bytes(4, "big") + b"x" * (SIZE - 4))
    s.close(); t.join()
    print(f"TCP: 送信 {N*SIZE:,} バイト → 受信 {total:,} バイト(送る側が受け手の速さに合わせて待つ)")

udp_test(); tcp_test()
UDP: 送信 10000 個 → 受信 356 個(失われた 9644 個)  順番の入れ替わり 0 回
TCP: 送信 10,000,000 バイト → 受信 10,000,000 バイト(送る側が受け手の速さに合わせて待つ)
$ nstat -az UdpRcvbufErrors UdpInDatagrams
#kernel
UdpInDatagrams                  356                0.0
UdpRcvbufErrors                 9644               0.0

UDP は1万個のうち 9,644 個が失われ、送った側には何のエラーも出ませんでした。 カーネルの統計 UdpRcvbufErrors(受信バッファがあふれて捨てた数)が、失われた数とぴったり同じです。もう1回動かすと、受信は 251 個(失われた 9,749 個)でした。数は実行のたびに変わります。

TCP は同じ 10MB が1バイトも欠けずに届きました。受け手のバッファが埋まると、TCP は相手に「受け取れる量(ウィンドウ)」を小さく伝え、送る側の sendall がその分だけ待つからです(フロー制御)。

1UDP で欠けを見つける

上の例のように、データに通し番号を入れる。抜けた番号で失われたものが分かる

2UDP で送り直す

受け手が「受け取った番号」を返し、返事が無ければ送り直す。これを作り込んだものが QUIC

3捨てられた数を見る

Linux は nstat の UdpRcvbufErrors、netstat -su の receive buffer errors で確かめる

ポート番号とは(範囲とエフェメラルポート)

この節では、ポート番号の範囲の意味と、クライアント側のポートがどう決まるのかが分かります。

ポート番号は、1台のコンピューターの中で「どのプログラム宛てか」を区別する 0〜65535 の番号です。 TCP と UDP で別々に数えるので、TCP の53番と UDP の53番は別のポートです(DNS は両方を使います)。

範囲 名前(IANA) 使われ方
0〜1023 System Ports(well-known) HTTP 80・HTTPS 443・SSH 22・DNS 53 など
1024〜49151 User Ports(registered) PostgreSQL 5432・MySQL 3306 など
49152〜65535 Dynamic / Private Ports クライアント側の一時的なポート
Linux の既定 32768〜60999 クライアントに OS が割り当てる範囲

IANA の範囲は RFC 6335 の決まりです。Linux がクライアントに割り当てる範囲(エフェメラルポート)は net.ipv4.ip_local_port_range で、カーネルのドキュメントの既定値は 32768〜60999 です。手元でも同じ値でした。

$ sysctl net.ipv4.ip_local_port_range net.ipv4.ip_unprivileged_port_start
net.ipv4.ip_local_port_range = 32768    60999
net.ipv4.ip_unprivileged_port_start = 1024

サービス名からポート番号を引くと、TCP と UDP で登録が違うことが分かります(Python の socket.getservbyname。中身は /etc/services です)。

http tcp 80
http udp service/proto not found
https tcp 443
https udp 443
domain tcp 53
domain udp 53
ntp udp 123

HTTPS に UDP の443番があるのは、HTTP/3(QUIC)が UDP の443番を使うためです。よく使うポートの一覧は、関連記事「ポート番号一覧【2026年版】well-knownポートと確認コマンドの早見表」で扱っています。

ソケット通信のよくあるエラーと直し方

この節では、ソケットで出るエラーの原文から、原因と直し方を引けます。すべて手元で実際に起こしたエラーです。

エラー原文(Linux・Python) 原因 直し方
[Errno 98] Address already in use そのポートを使うプロセスか TIME-WAIT が残っている ss で探して止める・SO_REUSEADDR
[Errno 111] Connection refused 相手のポートで誰も待ち受けていない サーバーの起動・ポート・bind 先を確かめる
TimeoutError('timed out') 自分で決めた時間内に応答が無い 相手の IP・ファイアウォール・経路を確かめる
[Errno 110] Connection timed out OS の再送をすべて使い切った 同上。connect に時間制限を付ける
[Errno 113] No route to host 同じネットワークに相手がいない IP アドレスの間違い・相手の電源やネットワーク
[Errno 104] Connection reset by peer 相手が RST で強制的に切った 相手のログ・タイムアウト・落ちていないか
[Errno 32] Broken pipe 相手が閉じた接続に送った 送る前に切断を検知する・再接続する
[Errno 13] Permission denied 一般ユーザーが1023番以下を bind 1024番以上を使う・リバースプロキシを前に置く
[Errno 99] Cannot assign requested address 自分に無い IP アドレスに bind ip addr にある IP か 0.0.0.0 を使う
[Errno 90] Message too long UDP のデータグラムが大きすぎる 小さく分けて送る

Address already in use(ポートが使用中)

同じポートでサーバーを2つ起動すると、2つ目の bind で止まります。

$ python3 tcp_server.py   # 2つ目を同じポートで起動
Traceback (most recent call last):
  ...
    srv.bind((HOST, PORT))
OSError: [Errno 98] Address already in use

どのプロセスが使っているかは、OS ごとに次のコマンドで探します。

# Linux
ss -tlnp 'sport = :34901'
# Mac
lsof -nP -iTCP:34901 -sTCP:LISTEN
# Windows(PowerShell)
Get-NetTCPConnection -LocalPort 34901 | Select-Object LocalAddress,LocalPort,State,OwningProcess

もう1つの原因が TIME-WAIT です。サーバー側から先に接続を閉じると、サーバーのポートに TIME-WAIT が残り、すぐに起動し直すと bind に失敗します。 試したコードです。

import socket, sys, time
PORT = 34904
reuse = len(sys.argv) > 1 and sys.argv[1] == "reuse"

def serve_once(reuse):
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    if reuse:
        srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind(("127.0.0.1", PORT))
    srv.listen()
    cli = socket.create_connection(("127.0.0.1", PORT))
    conn, _ = srv.accept()
    conn.close()          # サーバー側から先に閉じる → サーバー側が TIME-WAIT になる
    cli.close()
    srv.close()

serve_once(reuse)
time.sleep(0.2)
try:
    serve_once(reuse)     # すぐに同じポートで起動し直す
    print(f"SO_REUSEADDR={'あり' if reuse else 'なし'}: 2回目の bind も成功")
except OSError as e:
    print(f"SO_REUSEADDR={'あり' if reuse else 'なし'}: 2回目の bind で {e!r}")
$ python3 timewait.py
SO_REUSEADDR=なし: 2回目の bind で OSError(98, 'Address already in use')
$ ss -tano state time-wait 'sport = :34904'
Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
0      0          127.0.0.1:34904    127.0.0.1:44122 timer:(timewait,59sec,0)

$ python3 timewait.py reuse      # TIME-WAIT が消えてから
SO_REUSEADDR=あり: 2回目の bind も成功

SO_REUSEADDR を付けると、TIME-WAIT が残っていても bind できました。 ただし、SO_REUSEADDR を付けずに作った接続の TIME-WAIT が残っている間は、新しいソケットだけに SO_REUSEADDR を付けて起動しても、同じ Address already in use で失敗しました。Linux では先に作られた側のソケットにも付いている必要があるので、サーバーは最初から SO_REUSEADDR を付けておくのが確実です。Python の socket.create_server() は、ドキュメントのとおり POSIX の環境では SO_REUSEADDR を自動で付けます。Windows・Mac・Docker も含めた探し方と止め方は、関連記事「Address already in useの解決方法【Windows・Mac・Linux】」で扱っています。

Connection refused(接続が拒否された)

誰も待ち受けていないポートに TCP でつなぐと、すぐに拒否されます。

$ python3 -c "import socket; socket.create_connection(('127.0.0.1', 34905), timeout=3)"
ConnectionRefusedError: [Errno 111] Connection refused
$ nc -vz 127.0.0.1 34905
nc: connect to 127.0.0.1 port 34905 (tcp) failed: Connection refused
$ curl http://127.0.0.1:34905/
curl: (7) Failed to connect to 127.0.0.1 port 34905 after 0 ms: Couldn't connect to server

tcpdump で見ると、SYN に対して相手の OS が RST(Flags [R.])を返しています。

22:28:18.354861 IP 127.0.0.1.37194 > 127.0.0.1.34905: Flags [S], seq 2458871218, win 65495, options [mss 65495,sackOK,TS val 2481444604 ecr 0,nop,wscale 10], length 0
22:28:18.354869 IP 127.0.0.1.34905 > 127.0.0.1.37194: Flags [R.], seq 0, ack 2458871219, win 0, length 0

RST が返るということは、相手のコンピューターまでは届いているということです。サーバーが起動しているか、ポート番号が合っているか、サーバーが 127.0.0.1 だけに bind していて外から受けられない状態になっていないか(0.0.0.0 にすると全部の IP で受ける)を確かめます。

UDP では、誰もいないポートに送ると、相手の OS が ICMP の「port unreachable」を返します。

22:28:18.826662 IP 127.0.0.1.53951 > 127.0.0.1.34905: UDP, length 5
22:28:18.826672 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 udp port 34905 unreachable, length 41

ところが、sendto で送っただけのソケットは、この ICMP を受けても recvfrom が TimeoutError: timed out になるまで待ち続けました。UDP のソケットで先に connect() して宛先を決めておくと、同じ状況で ConnectionRefusedError: [Errno 111] Connection refused を受け取れました。 UDP の connect は接続を作るのではなく、宛先を固定して、その宛先からのエラーを受け取れるようにする操作です。

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.settimeout(2)
s.connect(("127.0.0.1", 34905))   # 宛先を固定する(パケットは流れない)
s.send(b"hello")                  # send は成功する
s.recv(1024)                      # → ConnectionRefusedError: [Errno 111] Connection refused

timed out・No route to host(応答が返ってこない)

応答をまったく返さない相手は、ip netns の中に「存在しない MAC アドレスを持つ IP」を作って再現しました。Python のタイムアウトを5秒にしたときと、何も指定しないときの結果です。

10.49.0.9: 5.0秒後に TimeoutError('timed out')
10.49.0.5: 3.1秒後に OSError(113, 'No route to host')
$ curl --connect-timeout 3 http://10.49.0.9:34911/
curl: (28) Failed to connect to 10.49.0.9 port 34911 after 3002 ms: Timeout was reached
$ nc -vz -w 3 10.49.0.9 34911
nc: connect to 10.49.0.9 port 34911 (tcp) timed out: Operation now in progress

10.49.0.5 は同じネットワークに誰もいない IP で、MAC アドレスを調べる問い合わせ(ARP)に答えが無いため、約3秒で No route to host になりました。

タイムアウトを指定しないと、Linux は SYN を送り直し続け、133.9 秒後にやっと TimeoutError(110, 'Connection timed out') になりました。 そのときの SYN を tcpdump で並べると、送り直しの間隔が伸びていく様子が見えます。

回 送った時刻(最初の SYN からの秒) 前との間隔
最初の SYN 0 ―
再送 1〜5 1.0・2.1・3.1・4.1・5.1 約1秒ずつ
再送 6 7.1 2秒
再送 7 11.3 4秒
再送 8 19.5 8秒
再送 9 35.6 16秒
再送 10 68.4 33秒
あきらめた 133.9 約65秒

この環境の設定は net.ipv4.tcp_syn_retries = 6・net.ipv4.tcp_syn_linear_timeouts = 4 でした。2分以上待たされないよう、クライアントは必ず接続のタイムアウトを指定します(Python の timeout=、curl の --connect-timeout、Java の connect(addr, ミリ秒))。Connection refused と Read timed out の違いと切り分けは、関連記事「Connection refused・Read timed outの違いと直し方」で扱っています。

Connection reset by peer・Broken pipe(途中で切れた)

相手が接続を強制的に切る(RST を送る)と Connection reset by peer、相手が閉じた接続に送り続けると Broken pipe になります。

import socket, struct, time

srv = socket.create_server(("127.0.0.1", 34908))

# 1) 相手が SO_LINGER=0 で閉じる → RST が届き、こちらの recv が失敗する
c = socket.create_connection(("127.0.0.1", 34908)); s, _ = srv.accept()
s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", 1, 0))
s.close()
time.sleep(0.1)
try:
    c.recv(1024)
except OSError as e:
    print("recv:", repr(e))

# 2) 相手が閉じたあとも送り続ける → 1回目は届いたように見え、RST を受けた後の送信が失敗する
c = socket.create_connection(("127.0.0.1", 34908)); s, _ = srv.accept()
s.close()
time.sleep(0.1)
for i in range(1, 4):
    try:
        c.sendall(b"data"); print(f"send {i}回目: 成功(エラーなし)")
    except OSError as e:
        print(f"send {i}回目:", repr(e)); break
    time.sleep(0.1)
recv: ConnectionResetError(104, 'Connection reset by peer')
send 1回目: 成功(エラーなし)
send 2回目: BrokenPipeError(32, 'Broken pipe')

相手が閉じたあとの1回目の send はエラーにならず、2回目で初めて失敗しました。 1回目のデータに相手の OS が RST で答え、それを受けたあとの送信がエラーになるためです。「送れたから届いた」とは限らないので、大事なやり取りはアプリの側で返事(応答)を確かめます。

Permission denied・Cannot assign requested address(bind のエラー)

$ runuser -u nobody -- python3 -c "import socket; socket.socket().bind(('127.0.0.1', 80))"
PermissionError: [Errno 13] Permission denied
$ python3 -c "import socket; socket.socket().bind(('192.0.2.10', 34909))"
OSError: [Errno 99] Cannot assign requested address

Linux では net.ipv4.ip_unprivileged_port_start(この環境では 1024)より小さいポートは、root などの権限が無いと bind できません。自分のコンピューターに無い IP アドレス(ここでは 192.0.2.10)には bind できません。

Windows のエラー番号(Winsock)

Windows では同じ原因が [WinError 10061] のような番号で出ます。Microsoft Learn の「Windows ソケットのエラー コード」の表記です。

番号 名前 Microsoft Learn の説明(冒頭)
10048 WSAEADDRINUSE アドレスは既に使用されています。
10061 WSAECONNREFUSED 接続が拒否されました。
10060 WSAETIMEDOUT 接続がタイムアウトしました。
10054 WSAECONNRESET ピアによる接続のリセット。

つながらないときに確かめる順番

  1. 1サーバー側で待ち受けているかss -tlnp(UDP は -ulnp)でポートと bind 先の IP(127.0.0.1 か 0.0.0.0 か)を見る
  2. 2同じマシンからつながるかサーバー上で nc -vz 127.0.0.1 ポート。ここで refused ならアプリの問題
  3. 3外からつないで、エラーの種類を見るrefused は相手まで届いている。timed out は途中で消えている(ファイアウォール・経路)
  4. 4パケットを見るtcpdump で SYN が届いているか、RST や ICMP が返っているかを両側で見る

ソケットとHTTP・WebSocketの違い

この節では、普段使う HTTP や WebSocket が、ソケットとどういう関係にあるのかが分かります。

HTTP も WebSocket も、TCP のソケットの上でやり取りする「話し方の決まり」です。 ソケットは土台の通り道で、HTTP や WebSocket はその上を流れるデータの形を決めています。

HTTP は TCP ソケットに流す文字列

Python の http.server を起動し、ライブラリを使わずに TCP のソケットで HTTP のリクエストを送りました。

import socket

with socket.create_connection(("127.0.0.1", 34915), timeout=5) as s:
    s.sendall(b"GET /index.html HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n")
    res = b""
    while (chunk := s.recv(4096)):
        res += chunk
print(res.decode())
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.11.15
Date: Thu, 22 Oct 2026 13:32:23 GMT
Content-type: text/html
Content-Length: 13
Last-Modified: Thu, 22 Oct 2026 13:32:22 GMT

hello socket

ブラウザや curl も、裏では同じように TCP のソケットにこの文字列を書き込んでいます。HTTPS は、TCP のソケットの上で TLS の暗号化をしてから同じ文字列を流します。HTTP/3 は TCP ではなく UDP の上の QUIC を使います(RFC 9114)。

WebSocket は HTTP から切り替える双方向の通信

WebSocket(RFC 6455)は、最初に HTTP で「WebSocket に切り替えたい」と頼み、サーバーが 101 Switching Protocols で応じたあとは、同じ TCP 接続でサーバーとブラウザがいつでも送り合える形になります。Python の websockets 17.3 でエコーサーバーとクライアントを動かし、tcpdump で最初のやり取りを取り出しました。

import asyncio
from websockets.asyncio.server import serve
from websockets.asyncio.client import connect

async def echo(ws):
    async for msg in ws:
        await ws.send(f"ECHO: {msg}")

async def main():
    async with serve(echo, "127.0.0.1", 34914):
        async with connect("ws://127.0.0.1:34914/chat") as ws:
            for m in ["こんにちは", "WebSocket"]:
                await ws.send(m)
                print("返信:", await ws.recv())

asyncio.run(main())
返信: ECHO: こんにちは
返信: ECHO: WebSocket
GET /chat HTTP/1.1
Host: 127.0.0.1:34914
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: SSvlY7dWHGK8jO+QuGLGIw==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
User-Agent: Python/3.11 websockets/17.3

HTTP/1.1 101 Switching Protocols
Date: Thu, 22 Oct 2026 13:32:12 GMT
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: H08QHH54H3RRJMPjwe7bK+qi6qc=
Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=12; client_max_window_bits=12
Server: Python/3.11 websockets/17.3

その前には TCP の SYN・SYN+ACK が流れていて、WebSocket が TCP の接続の上で始まることが分かります。Sec-WebSocket-Accept は、Sec-WebSocket-Key に RFC 6455 で決められた文字列(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)をつなげて SHA-1 を取り、Base64 にした値です。上のキーで計算すると、同じ H08QHH54H3RRJMPjwe7bK+qi6qc= になりました。RFC 6455 の例のキー dGhlIHNhbXBsZSBub25jZQ== では s3pPLMBiTxaQ9kYGzzhZRbK+xOo= になり、RFC の値と一致します。

項目 ソケット(TCP) HTTP WebSocket
何か OS の通信の出入口 TCP の上の要求と応答の決まり TCP の上の双方向の決まり
送る単位 バイト列(区切りなし) リクエストとレスポンス メッセージ(フレーム)
サーバーから送り始める できる できない(要求への応答だけ) できる
ブラウザから使う 使えない fetch など WebSocket API
主な用途 独自の通信・DB の接続 Web ページ・API チャット・通知・共同編集

ブラウザの JavaScript は、ソケットを直接開けません。ブラウザからサーバーへ常につながった通信をしたいときに使うのが WebSocket です。Node.js での WebSocket サーバーの作り方は、関連記事「Node.js WebSocket実装|ws・再接続・認証・nginxまで」で扱っています。

言語ごとのソケットの書き方(Java・Node.js・C・Go)

この節では、Python 以外の言語でソケットを使うときの名前の対応が分かります。仕組みは同じなので、言語が違っても同じサーバーとやり取りできます。

言語 TCP のサーバー TCP のクライアント UDP
C(POSIX) socket・bind・listen・accept socket・connect socket(SOCK_DGRAM)・sendto
Python socket.create_server socket.create_connection socket(SOCK_DGRAM)
Java ServerSocket Socket DatagramSocket
Node.js net.createServer net.connect dgram.createSocket
Go net.Listen("tcp", …) net.Dial("tcp", …) net.ListenUDP・net.Dial("udp", …)

Python の TCP サーバー(34901番)に、Java(OpenJDK 21.0.10)と Node.js(22.22.2)のクライアントからつなぎました。

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;

public class EchoClient {
    public static void main(String[] args) throws IOException {
        try (Socket s = new Socket()) {
            s.connect(new InetSocketAddress("127.0.0.1", 34901), 3000); // 接続のタイムアウト 3秒
            s.setSoTimeout(3000);                                       // 受信のタイムアウト 3秒
            OutputStream out = s.getOutputStream();
            InputStream in = s.getInputStream();
            out.write("Javaから".getBytes(StandardCharsets.UTF_8));
            byte[] buf = new byte[1024];
            int n = in.read(buf);
            System.out.println("返信: " + new String(buf, 0, n, StandardCharsets.UTF_8));
            System.out.println("自分のポート: " + s.getLocalPort());
        }
    }
}
$ java -Dstdout.encoding=UTF-8 EchoClient.java
返信: ECHO: Javaから
自分のポート: 42422
const net = require("node:net");
const s = net.connect({ host: "127.0.0.1", port: 34901 }, () => s.write("Nodeから"));
s.setTimeout(3000, () => s.destroy(new Error("timeout")));
s.on("data", (d) => { console.log("返信:", d.toString()); s.end(); });
s.on("error", (e) => console.error(e.code, e.message));
$ node echo_client.js
返信: ECHO: Nodeから

Node.js で誰もいないポートにつなぐと、エラーは ECONNREFUSED connect ECONNREFUSED 127.0.0.1:34905 でした。言語ごとに表示は違っても、中身は同じ OS のエラー(ECONNREFUSED・番号111)です。

よくある質問

Q. ソケットとは簡単に言うと何ですか? A. プログラムがネットワークで通信するための出入口です。IP アドレス(どのコンピューターか)とポート番号(どのプログラムか)の組で決まり、プログラムはソケットに書き込み・読み出しするだけで通信できます。

Q. ソケット通信とは何ですか? A. ソケットを使ってプログラム同士がデータをやり取りすることです。サーバーは socket・bind・listen・accept、クライアントは socket・connect の順に OS に頼んでつなぎ、send と recv で送り合います。

Q. ソケットとポートの違いは? A. ポートはコンピューターの中の「部屋番号」(0〜65535 の数字)で、ソケットはその番号を使ってプログラムが持つ通信の出入口です。1つのサーバーのポートに、接続の数だけソケットが作られます。

Q. TCPとUDPはどちらが速いですか? A. 1回のやり取りは、接続・確認・切断が無い UDP のほうが少ないパケットで済みます。この記事の例では TCP が12個、UDP が4個でした。ただし UDP は欠けても送り直さないので、全部を確実に届けたい用途では TCP を使います。

Q. UDPとは何ですか?なぜ使うのですか? A. 接続を作らずにデータグラムを1つずつ送るプロトコルで、ヘッダーは8バイトです。遅れて届くより欠けたほうがよい通話・動画・ゲームや、1往復で済む DNS・NTP で使われます。HTTP/3 も UDP(QUIC)の上で動きます。

Q. TCPとUDPで同じポート番号を同時に使えますか? A. 使えます。TCP と UDP のポートは別々に数えるので、DNS のように TCP の53番と UDP の53番を両方使うこともできます。

Q. ソケットとWebSocketの違いは? A. ソケットは OS の通信の出入口で、WebSocket は TCP のソケットの上で双方向にやり取りするための決まり(RFC 6455)です。WebSocket は HTTP で始まり、101 Switching Protocols のあとは同じ TCP 接続を使い続けます。

Q. ソケットとHTTPの違いは? A. HTTP は、TCP のソケットに流す「リクエストとレスポンスの書き方」の決まりです。この記事では、ソケットに GET /index.html HTTP/1.1 という文字列を書き込むだけで Web サーバーから応答が返りました。

Q. 3ウェイハンドシェイクとは? A. TCP が送る前に、SYN・SYN+ACK・ACK の3回のやり取りで接続を作る手順です。お互いの最初のシーケンス番号を伝え合い、以後の順番と抜けの確認に使います。

Q. UNIXドメインソケットとは? A. 同じコンピューターの中のプロセス同士で通信するためのソケットで、IP アドレスとポートの代わりにファイルのパス(例 /var/run/xxx.sock)で相手を指定します。ネットワークを通らないので、同じマシン内なら TCP より手軽に使われます。

Q. ソケット通信は暗号化されますか? A. TCP や UDP のソケットそのものは暗号化しません。暗号化するときは TCP の上で TLS(HTTPS など)、UDP の上で DTLS や QUIC を使います。

Q. Address already in use はどう直しますか? A. ss -tlnp(Mac は lsof -nP -iTCP:ポート -sTCP:LISTEN)で使っているプロセスを探して止めます。再起動直後に出るのは TIME-WAIT が原因のことが多く、サーバーのソケットに SO_REUSEADDR を付けると避けられます。

まとめ

  • ソケットは通信の出入口で、IP アドレス+ポート番号(+TCP か UDP か)で決まる。プログラムからは OS が渡すファイルディスクリプタの1つに見える。
  • サーバーは socket → bind → listen → accept、クライアントは socket → connect。accept は接続ごとに新しいソケットを返す。
  • TCP は接続・確認・再送・順番・フロー制御があり、データは区切りの無いバイト列。UDP はどれも無く、1回送った分が1個のデータグラムになる。
  • 同じやり取りで TCP は12パケット、UDP は4パケット。受け手より速く送ると、UDP は1万個中 9,644 個が失われ、TCP は 10MB が全部届いた。
  • TCP でメッセージを区切るには、先頭に長さを付けて読み切る。
  • Address already in use は使用中のプロセスか TIME-WAIT、Connection refused は待ち受けが無い(RST が返る)、timed out は応答が無い。タイムアウトを指定しないと Linux の connect は約134秒待った。
  • HTTP・WebSocket は TCP のソケットの上の決まり。HTTP/3 は UDP の上の QUIC を使う。

参考・出典

確認日はいずれも 2026年10月22日です。

  • RFC 9293「Transmission Control Protocol (TCP)」:https://www.rfc-editor.org/rfc/rfc9293.html
  • RFC 768「User Datagram Protocol」:https://www.rfc-editor.org/rfc/rfc768.html
  • RFC 6455「The WebSocket Protocol」:https://www.rfc-editor.org/rfc/rfc6455.html
  • RFC 6335「Internet Assigned Numbers Authority (IANA) Procedures for the Management of the Service Name and Transport Protocol Port Number Registry」:https://www.rfc-editor.org/rfc/rfc6335.html
  • RFC 9000「QUIC: A UDP-Based Multiplexed and Secure Transport」:https://www.rfc-editor.org/rfc/rfc9000.html
  • RFC 9114「HTTP/3」:https://www.rfc-editor.org/rfc/rfc9114.html
  • RFC 7766「DNS Transport over TCP - Implementation Requirements」:https://www.rfc-editor.org/rfc/rfc7766.html
  • Python ドキュメント「socket — Low-level networking interface」:https://docs.python.org/3/library/socket.html
  • The Linux Kernel documentation「IP Sysctl」:https://docs.kernel.org/networking/ip-sysctl.html
  • Linux ソース「include/net/tcp.h」:https://github.com/torvalds/linux/blob/master/include/net/tcp.h
  • Microsoft Learn「Windows ソケットのエラー コード」:https://learn.microsoft.com/ja-jp/windows/win32/winsock/windows-sockets-error-codes-2
  • websockets ドキュメント:https://websockets.readthedocs.io/

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「ソケットとは|TCPとUDPの違いをPythonの通信とtcpdumpで解説」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?