標準編で telnet を使って観察した HTTP / SMTP / POP3 / FTP の応答 ─ その正体に踏み込む発展回。サーバもクライアントも神秘的な箱ではなく、OS 上で動くただのプロセスです。サーバは TCP/UDP のポートを listen し、クライアントはそこへ connect する。ps / ss / lsof で実体を見て、Python で最小限のクライアント/サーバを動かし、Apache・nginx・BIND・Postfix などの daemon と同じ対応関係で理解します。本講のあと 第29回 HTTP プログラミング で「自分で HTTP を書く」 側に進み、続く 第30回 Wireshark でパケットを観る で「自分が書いた通信がワイヤをどう流れるか」を観察します。
本講を終えると、以下を達成できるようになります。
Web サーバとは「TCP 80 を listen して HTTP メッセージを処理するプロセス」のことです。実装は Apache HTTP Server でも nginx でも、Python の http.server でも、Go の net/http でもかまいません。HTTP というプロトコルに従って要求を読み、応答を書き返せば、ネットワーク上の相手からは Web サーバとして見えます。
図の見方:左のブラウザもプロセス、右の httpd / smtpd / named もプロセスです。サーバマシンが偉いのではなく、どの PID のプロセスがどのポートを listen しているか が実体です。
つながる知識: 標準編で telnet が接続していた相手は「HTTP という概念」ではなく、その瞬間に 80 番ポートを掴んでいた実行中プロセスです。だから nginx を止めれば 80 番の応答は消え、Python サーバを 8000 番で起動すれば 8000 番に新しい応答が現れます。
確認: 「サーバが応答する」という現象の本質として、最も適切なものはどれか。
正解:C。サーバとは「特定のポートで待ち受ける実行中プロセス」の役割名。プロトコルは概念、ポート番号は単なる 16 bit の識別子で、実体は「今そのポートを掴んでいる PID」。だから systemctl stop nginx すれば 80 番の応答は消える。
# Linux / macOS
ps -ef | grep -E 'httpd|nginx|named|postfix|dovecot|vsftpd' | grep -v grep
# BSD/macOS 風の表示が好みなら
ps aux | grep -E 'httpd|nginx|named|postfix|dovecot|vsftpd' | grep -v grep
# Linux: TCP の listen 中ソケットとプロセス
sudo ss -tlnp
# Linux: UDP も含めて見る
sudo ss -tulnp
# macOS: listen 中のプロセスを一覧
sudo lsof -i -P -n | grep LISTEN
# 特定ポートだけ見る
sudo lsof -i :80 -P -n
sudo systemctl start nginx
ps aux | grep nginx | grep -v grep
sudo ss -tlnp | grep ':80'
# 出力例
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1234,fd=6))
curl -I http://localhost/
# 出力例
HTTP/1.1 200 OK
Server: nginx
sudo systemctl stop nginx
sudo ss -tlnp | grep ':80' # 何も出なければ listen していない
Q. 自分の PC で nginx を 80 番で起動し、別ターミナルで ss -tlnp 'sport = :80' を実行すると何が見えるか? その後 sudo systemctl stop nginx してから同じコマンドを実行すると、表示はどう変わるか?
停止前: 80 番が LISTEN 状態で表示され、右端に users:(("nginx",pid=1234,fd=6)) のようにプロセス名と PID が出る(ポートを掴んでいるプロセスが特定できる)。
停止後: 行そのものが消える。誰も bind していないため OS のソケットテーブルからエントリが除去される。この状態で curl localhost:80 すると Connection refused になる(SYN を受け取る相手がいない)。
これが「サーバが動く」「サーバが止まる」の OS から見た実体。ポートは魔法ではなく、プロセスが持つ識別子。
抽象度を最も下げた例として、TCP ソケットを自分で作り、HTTP/1.1 のリクエスト行とヘッダを文字列で送ります。「接続=ソケット、応答=バイト列」という事実をまず体感する目的のコードです。
# raw_http_client.py
import socket
host = "example.com"
request = (
"GET / HTTP/1.1\r\n"
f"Host: {host}\r\n"
"Connection: close\r\n"
"\r\n"
)
with socket.create_connection((host, 80), timeout=5) as s:
s.sendall(request.encode("ascii"))
chunks = []
while True:
data = s.recv(4096)
if not data:
break
chunks.append(data)
print(b"".join(chunks).decode("utf-8", errors="replace")[:1000])
確認: クライアントが TCP で connect() するとき、自分側のポート番号(送信元ポート)はどう決まるか?
正解:B。クライアントが bind() せずに connect() すると、OS が空いている短命ポート(エフェメラルポート、Linux は通常 32768〜60999)から自動で割り当てる。これにより同じクライアントから同じサーバへ複数同時接続しても、4-tuple(送信元IP・送信元ポート・宛先IP・宛先ポート)が異なるので別接続として扱える。
# カレントディレクトリを http://localhost:8000/ で公開
python3 -m http.server 8000
# 別ターミナルから確認 ─ 「PID + listen ポート」が見える
curl -I http://localhost:8000/
lsof -i :8000 -P -n
ポイントは lsof -i :8000 の出力に 「python3 (PID) ─ TCP *:8000 (LISTEN)」 が現れること。これだけで 「Python プロセスが Web サーバになっている」 という事実が直接見えます。
標準編 第16回 telnet の手法で、自作サーバの応答も観察できます。
python3 -m http.server 8000 &
# 別ターミナル
telnet 127.0.0.1 8000
GET / HTTP/1.1
Host: localhost
Connection: close
# 空行まで送ると、HTTP/1.0 200 OK などの応答が返る
図の見方:Python プログラムも Apache も、ネットワークから見ると「HTTP を話すプロセス」です。違いは実装・性能・機能の厚みであって、ポートで待ち受けてリクエストに応答する という骨格は同じです。
Q. 同一 PC で Apache と nginx の両方を 80 番で同時起動しようとすると何が起きるか? 一方を 8080 番に変更すれば共存できるか? その場合、利用者から見て何が変わるか?
同じ 80 番: 後発の起動側が EADDRINUSE で失敗する。同じ IP・同じプロトコル・同じポートに 1 つの bind しかできないという OS の規則は強制で、回避できない(SO_REUSEPORT での負荷分散は同一プログラムが意図的に行う場合の例外)。
片方を 8080 に変更: 共存可能。ただし、利用者は http://example.com:8080 のようにポートを URL に明示する必要がある。80 や 443 が「特別」なのは 慣習として黙って動く だけで、OS から見れば 8080 と等価。
これは標準編で telnet を打って観察した「ポートに HTTP / SMTP / FTP が紐付いている」という見え方が 慣習であって絶対ではない という事実の裏返し。
| サーバ | 役割 | 主役プロトコル | プロセス名 | 既定ポート | 公式 URL |
|---|---|---|---|---|---|
| Apache HTTP Server | 古典的・モジュラな Web サーバ | HTTP / HTTPS | httpd / apache2 | 80 / 443 | httpd.apache.org |
| nginx | 高速・軽量な Web サーバ。リバースプロキシにも多用 | HTTP / HTTPS | nginx | 80 / 443 | nginx.org |
| BIND | 権威 DNS / キャッシュ DNS の双方を担える DNS サーバ | DNS | named | UDP/TCP 53 | isc.org/bind |
| Postfix | MTA。メールを受け取り、配送する | SMTP / Submission | master / smtpd | 25 / 587 | postfix.org |
| Dovecot | MDA / IMAP・POP3 サーバ。ユーザがメールを取り出す | IMAP / POP3 | dovecot / imap-login | 143 / 110 / 993 / 995 | dovecot.org |
| vsftpd / pure-ftpd | FTP サーバ。ファイル転送の制御接続とデータ接続を扱う | FTP | vsftpd / pure-ftpd | 21 / 20 | vsftpd / pure-ftpd |
| 対象 | 起動 | 挙動確認 |
|---|---|---|
| Apache | sudo apt install apache2 && sudo systemctl start apache2 | curl -I http://localhost/ |
| nginx | sudo apt install nginx && sudo systemctl start nginx | curl -I http://localhost/ |
| BIND | sudo apt install bind9 && sudo systemctl start bind9 | dig @localhost example.com |
| Postfix | sudo apt install postfix | telnet localhost 25 で 220 |
| Dovecot | sudo apt install dovecot-pop3d dovecot-imapd | telnet localhost 110 で +OK |
| vsftpd | sudo apt install vsftpd && sudo systemctl start vsftpd | ftp localhost または telnet localhost 21 |
1995 年生まれの Apache は 「リクエストごとにプロセス/スレッドを生成」 するモデル(prefork)で、CGI や PHP などのレガシー資産との親和性が高い。一方 2004 年生まれの nginx は 「1 プロセスでイベント駆動 I/O 多重化」 し、何万もの同時接続を 1 ワーカで捌ける設計。
同じ「HTTP サーバ」役でも、内部実装(プロセスモデル・I/O モデル)が違うので得意分野が分かれる。役割は同じ、実装の選択肢として複数ある、という見方。
図の見方:PID は OS の管理番号、ポートはネットワーク上の入口、プロトコルは会話の文法、実装プログラムは具体的なコードです。同じ HTTP でも実装プロセスは様々で、同じプロセスが複数ポートを listen することもあります。
Apache、nginx、Python の http.server はすべて HTTP 応答を返せます。機能・性能・設定方法は違いますが、HTTP を話すという意味では同じ役割です。
Dovecot は IMAP 143、IMAPS 993、POP3 110、POP3S 995 のように複数の入口を持てます。1 プロセス群が複数プロトコル・複数ポートを扱う構成もあります。
同じ IP・同じ TCP/UDP・同じポートは、通常 1 つのプロセスだけが bind できます。これがポート競合です。
確認: 「ポート 443 で動いているなら必ず HTTPS(TLS over TCP)である」という認識は正しいか?
正解:C。実際に HTTP/3(QUIC)は 443/UDP を使うし、gRPC やカスタムプロトコルを 443 で動かす運用もある。ポート番号と上位プロトコルの対応は 慣習(/etc/services など)で IANA が登録番号を管理しているだけ。OS は「このポートはこのプロトコル専用」とは強制しない。だから telnet で 443 に繋いで生バイトを送ると(TLS でないため)サーバ側でエラー応答になる。
# 稼働状態を見る
systemctl status apache2
# 設定を再読み込みする。接続中の処理をできるだけ保つ
sudo systemctl reload apache2
# プロセスを止めて起動し直す
sudo systemctl restart apache2
# ログを見る
journalctl -u apache2 -f
図の見方:クライアントから見える入口は nginx の 80 / 443 番だけでも、その裏で複数の Apache プロセスやアプリサーバが動く構成があります。ロードバランサやリバースプロキシも、結局は 別のポートへ接続するプロセス です。
確認: 「同じプログラムが複数のプロセスとして同時起動できる」例として、ポート競合を起こさず確実に成立するのはどれか。
正解:A。ポートが違えば同じプログラムでも別プロセスとして共存可能(リバースプロキシで内側に複数の HTTP サーバを動かす構成は実運用でよくある)。B・C は同じポート競合で後発が EADDRINUSE 失敗。D は誤り(プロセスは独立した実行単位なので、リソースが許せば同じバイナリの複数起動は普通に可能)。
| 用語 | 1 行説明 |
|---|---|
| プロセス | OS 上で実行中のプログラム。PID を持ち、CPU/メモリなどの資源を使う |
| listen | サーバ側プロセスがポートで接続待ちする状態 |
| bind | ソケットにローカル IP アドレスとポート番号を結び付ける操作 |
| socket | アプリがネットワークと読み書きする窓口。IP アドレス・ポート・プロトコルと関係する |
| daemon | バックグラウンドで常駐し、要求を待つサーバプロセス |
| MTA | Mail Transfer Agent。メールを受け取り、配送する役割。Postfix など |
| MDA | Mail Delivery / Retrieval 側の役割として、ユーザがメールを取り出せるようにする。Dovecot など |
| リバースプロキシ | クライアントからの入口になり、裏側の別サーバへ要求を転送するサーバプロセス。nginx が代表例 |
| daemon | 主役プロトコル | 典型ポート |
|---|---|---|
| httpd / apache2 | HTTP / HTTPS | 80 / 443 |
| nginx | HTTP / HTTPS | 80 / 443 |
| named | DNS | 53(UDP/TCP) |
| master / smtpd(Postfix) | SMTP / Submission | 25 / 587 |
| dovecot / imap-login | IMAP / POP3 | 143 / 110 / 993 / 995 |
| vsftpd / pure-ftpd | FTP | 21 / 20 |