アプリ開発者のための
TCP/IP ネットワーク入門 (反転バージョン)

Yusuke Shinyama, Nov. 2023

概要: この記事では、アプリ開発者の役に立ちそうな TCP/IPネットワークに関する最低限の知識をまとめた。 あくまで視点は「アプリ開発者」なので、 現在の家庭・企業における一般的なネットワーク環境しか想定していないし、 セキュリティに関しても基本的なことしかカバーしていない。 ネットワーク機器の具体的な設定方法や診断については、より専門的な資料を参照のこと。

オリジナル版との違い: オリジナル版では、ネットワークの低水準な層 (データリンク層など) から説明していた。 反転バージョンでは高水準な層 (HTTP) から先に説明する。

  1. HTTP の仕組み
  2. curl コマンドの使い方
  3. 実際の通信
  4. 人間HTTPクライアント
  5. 人間HTTPサーバ
  6. うまく動かないときは
  7. 用語一覧

1. HTTP の仕組み

ウェブはもっとも有名なインターネットの使い方 (アプリケーション) である。 これは HTML型式で書かれたデータ (ようするに、Webページ) を、 HTTP (Hyper Text Ternsfer Protocol) と 呼ばれる方式によってブラウザに転送する。

演習. ブラウザを使って「リクエスト」と「レスポンス」を観察する
  1. ブラウザで F12キーを押して開発用コンソールを開き、"Network" タブをクリック。
  2. タブ内で http://www.example.com/index.html を開く。
  3. 右側にリクエスト一覧が表示される。
  4. index.html を選択し、その横の Headers タブをクリック。
  5. "Response Headers" および "Request Headers" が表示される。
  1. webブラウザ (クライアント) がコンテンツ要求 (リクエスト) を送信する。
  2. webサーバが応答 (レスポンス) を送信し、つづいてコンテンツのデータを送る。
  3. 読み込むページや画像の数だけ 1., 2. を繰り返す。
webブラウザ (クライアント) webサーバ リクエスト レスポンス
HTTPにおけるリクエストとレスポンス
結論: HTTP = リクエストとレスポンス (の繰り返し)

2. curl コマンドの使い方

では、HTTPにおけるリクエストとレスポンスの正体は何か? 実はただの文字列である。 これは curlコマンドを使うと明らかになる。

演習. curlコマンドを使ってリクエストとレスポンスを観察する
  1. ターミナルを開く。
  2. 以下のコマンドを実行する:
    $ curl -v http://www.example.com/index.html
    * Trying 93.184.216.34:80...
    * Connected to www.example.com (93.184.216.34) port 80 (#0)
    
    > GET /index.html HTTP/1.1 > Host: www.example.com > User-Agent: curl/8.1.2 > Accept: */* >
    < HTTP/1.1 200 OK < Accept-Ranges: bytes < Age: 31773 < Cache-Control: max-age=604800 < Content-Type: text/html; charset=UTF-8 < Date: Tue, 10 Oct 2023 09:11:50 GMT < Etag: "3147526947" < Expires: Tue, 17 Oct 2023 09:11:50 GMT < Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT < Server: ECS (laa/7B10) < Vary: Accept-Encoding < X-Cache: HIT < Content-Length: 1256 <
    <!doctype html> <html> <head> <title> Example Domain</title> <meta charset="utf-8" /> ...
  3. 出力から以下の各部分を確認する:
    • リクエスト
    • レスポンス
    • ボディ (ペイロード)

2.1. HTTPリクエストの正体

ブラウザ (あるいはcurlコマンド) で

http://www.example.com/index.html
というURL を開くと、サーバに以下のようなリクエスト文字列が送信される:

GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 6.1;) Gecko/20100101
...

GET で始まる最初の行がリクエストの本体を表す。 続く Host:User-Agent: などの行は 付随情報で、リクエストヘッダと呼ばれる:

2.2. HTTPレスポンスの正体

サーバはHTTPリクエストを受け取ると、 以下のようなレスポンス文字列を返す:

HTTP/1.1 200 OK
Accept-Ranges: bytes
Age: 31773
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Tue, 10 Oct 2023 09:11:50 GMT
...

HTTP/1.1 で始まる最初の行にある 200 という数字がステータスコードと呼ばれる重要な部分で、 続く Accept-Ranges:Content-Type: などの行は 付随情報 (レスポンスヘッダ) と呼ばれる:

ヘッダの後には、 ボディ (body) あるいは ペイロード (payload) と 呼ばれるコンテンツ本体のデータが続く。 これは通常 HTML ファイルや画像ファイルの内容そのものである。 ブラウザはこの内容を解析して表示すると、ページが表示されたことになる。

いろいろな HTTP ステータスコード

正常なレスポンスの場合、最初の一行は必ず

HTTP/1.1 200 OK
となっているが、これ以外のステータスコードが返ってくる場合もある。

たとえば、指定されたファイルが存在しない (いわゆる「ページが見つかりません」エラー) 場合はステータスコードとして 404 が返される。

HTTP/1.1 404 Not Found

あるいは「別の場所を参照せよ」という意味の 301 が返されることもある。 これが返されると、ブラウザのアドレスバーが変化する。

HTTP/1.1 301 Moved
演習. HTTPステータスコードを観察する

curlを使って以下のURLにリクエストを送り、どのようなステータスコードが返されるのか確認しよう。

補足. クッキーとは

通常、ひとつのページを表示するために、ブラウザが同一のサーバに複数のリクエストを送ることは多い。 HTTPの各リクエスト・レスポンスは、多くの場合、単体で完結しており、 各リクエスト・レスポンスの間に直接の関連はない。 しかし クッキー (cookie) という仕組みを使うことで、 一連のリクエストに「文脈」を追加することができる。 クッキーは実際にはブラウザ内に保存される文字列で、 HTTP のリクエストとレスポンス内のヘッダを使ってやりとりされる。

  1. ブラウザがサーバにリクエストを送る。
  2. サーバが、レスポンス中の Set-Cookie: ヘッダを使ってクッキーをブラウザに送る。
  3. ブラウザはクッキーを受け取り、それを記録しておく。
  4. 次回リクエスト時に、ブラウザは Cookie: ヘッダを使ってクッキーをサーバに送る。
  5. サーバはクッキーを受け取り、以前の文脈を知る。
  6. 2. に戻る。
webブラウザ webサーバ リクエスト レスポンス Set-Cookie: Cookie: Set-Cookie: Cookie: Set-Cookie:
HTTPクッキーのやりとり

実際のクッキーにはそれを受け取ったサーバ名(正確にはドメイン名)や有効期限といった情報が 付属しており、クライアントは特定のサーバに対して有効なクッキーだけを送るようになっている。

3. 実際の通信

では、curlコマンドはいったいどうやってHTTPリクエストを送っているのか? 答えは TCP である。TCP (一般的には関連規格を含めて TCP/IP と呼ぶ) は インターネット上のコンピュータが通信するための汎用的な規格で、 現在はほとんどのPC/スマートフォン/ゲーム機等で利用可能である。 ncコマンドは、TCP/IP を使って原始的な通信をおこなうプログラムである。

演習. TCP を使って通信してみる
  1. まず、ターミナルを2つ開く。
  2. ひとつのターミナルで、以下のコマンドを実行する:
    $ nc -l 10000
    
  3. もうひとつのターミナルで、以下のコマンドを実行する:
    $ nc localhost 10000
    
  4. ふたつのターミナルで何らかのテキストを入力し、 お互いのターミナルで双方向に通信が可能なことを確認せよ。

何が起こっているかというと、 ここでは localhost (自分のPC) 上の 2つのプロセス (nc) が互いに TCP の 10000番ポートを使って通信している。

localhost プロセス1 (サーバ) プロセス2 (クライアント) 10000番で 待ち受け 10000番に 接続
TCP/IPを使って2つのプロセス間で通信する

4. 人間HTTPクライアント

上で見たように、HTTPリクエストやレスポンスの本質はテキストである。 実は curl を使わなくても、人間が ncコマンドを使って 人手で HTTPリクエストを送ることができる。 つまり www.example.com の 80番に接続し、 文字列を入力すればよいのである。

localhost www.example.com クライアント サーバ 80番で 待ち受け 80番に 接続
ncコマンドを使って手動で HTTP リクエストを送る
演習. 人間HTTPクライアント

nc コマンドを使って、 www.example.com サーバに 「人間HTTPクライアント」としてリクエストを送り、 サーバ側のレスポンスを確認せよ。

$ nc www.example.com 80
GET /index.html HTTP/1.0 Host: www.example.com (空行)
HTTP/1.0 200 OK Content-Type: text/html; charset=UTF-8 Date: Tue, 10 Oct 2023 10:31:27 GMT ...
<!doctype html> <html> <head> <title> Example Domain</title> ...

80番は、HTTP で使われる「お約束の」ポート番号として 国際的に定められている。 よく知られている標準のポート番号として、以下のようなものがある (cf. TCPやUDPにおけるポート番号の一覧):

なお、これらのポート番号はあくまで慣例であり、 別のポートを使ってもかまわない。 たとえば、開発環境用の webサーバは、 通常 80番ポートではなく、8080番ポート (や 5132番ポート) で動いている。

localhost 開発用 webサーバ ブラウザ (クライアント) 8080番で 待ち受け 8080番に 接続
開発環境におけるweblサーバとクライアント

そのため、このようなサーバにアクセスするときには 以下の URL がよく使われる。

http://localhost:8080/
これは「(80番のかわりに) localhost の 8080番ポートに接続せよ」 という意味である。

5. 人間HTTPサーバ

人間が手動で HTTP リクエストを送れるのだから、 同様に ncコマンドを使って、人間が web サーバの「ふり」をすることもできるのではないか? 実際にやってみよう。

演習. 人間HTTPサーバ

nc コマンドを使って、 「人間HTTPサーバ」としてブラウザからの リクエストに返答せよ。 まず nc をサーバとして起動し、 ブラウザから http://localhost:8080/index.html という URLに接続してリクエストが送られるのを確認してから、 応答を入力せよ。

$ nc -l 8080
GET /index.html HTTP/1.1 User-Agent: Mozilla/5.0 ...
HTTP/1.0 200 OK Content-Type: text/html
<div>Hello.</div> (Control-D を押して終了)

以上のように、難しく見える HTTPの仕組みも、 実はただのテキスト処理だということがわかる。

おまけ: プログラムから HTTP を使う

ほとんどのプログラミング言語では HTTP のリクエストやレスポンスを 処理する機能が標準で含まれている。

演習. Node.js を使った HTTP クライアントとサーバ
  1. ターミナルを 2つ開く。
  2. ひとつのターミナルで、以下のコードを実行する:
  3. $ node server.js
    server.js (サーバ側)
    let http = require('http');
    let server = http.createServer((request, response) => {
      console.log("connected!");
      response.writeHead(200, {'Content-Type': 'text/html'});
      response.end('<h1>Hello world</h1>');
    });
    server.listen(8080);
    
  4. もうひとつのターミナルで、以下のコードを実行する:
  5. $ node client.js
    client.js (クライアント側):
    (async () => {
      let response = await fetch('http://localhost:8080/');
      let text = await response.text();
      console.log(text);
    })();
    

6. うまく動かないときは

HTTP や TCP による通信は現代のシステムでは簡単に扱えるが、 実はその仕組みは非常に複雑である。 したがって、うまく動かない時のために、 ある程度の内部的な原理を知っておくことは重要である。

HTTP TCP層 IP層 データリンク層 ケーブル・WiFi

HTTPのエラー

HTTP のエラーは、サーバが (一応) 動いてはいるが、 クライアントのリクエストを正しく処理できなかったときに起こる。 多くの場合、HTTP ステータスコードとして以下のような値を返す:

TCP層のエラー

TCPのエラーでよくあるのは「接続先のサーバが起動していない」場合である。 たとえ PC の電源が入っていても、指定されたポート番号でサーバが 待ち受けていない (listenしていない) 場合は、接続は失敗する。

演習. TCPのエラーを体験する

サーバが起動してない状態で以下のコマンドを実行し、 TCP接続に失敗することを確認せよ。

$ nc -v localhost 8080
localhost クライアント ??? 8080番に 接続
サーバが待ち受けしていない場合、接続はできない!

IP層のエラー

TCPの通信は、IPパケットという細かい単位に分割されて 相手のコンピュータに送信されている。

ウェブ LINE メール 動画配信 11.22.33.44 55.66.77.88 123.45.200.10 20.10.8.5
IPパケット (想像図)

しかし、そもそもこの IPパケットが送れない (到達できない) 場合がある。 このような場合は、以下のような原因が考えられる:

このような場合「IPパケットが相手に正しく到達しているか?」を知るために pingコマンドが使える。

演習. pingコマンドでネットワークの導通を確認する

ping コマンドを使って、ネットワークの導通を確認してみよう。 (pingコマンドは永久に終わらないので、途中で Control + C を押して止める)

  1. まず、インターネットに接続されていれば www.example.com から応答が返ってくるはずである:
    $ ping www.example.com
    PING www.example.com (93.184.216.34) 56(84) bytes of data.
    64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=1 ttl=51 time=104 ms
    64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=2 ttl=51 time=104 ms
    64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=3 ttl=51 time=104 ms
    ^C
    --- www.example.com ping statistics ---
    4 packets transmitted, 4 received, 0% packet loss, time 3004ms
    rtt min/avg/max/mdev = 103.890/103.906/103.935/0.016 ms
    
  2. いっぽうで、たいていの環境ではアドレス 10.10.10.10 にはコンピュータが つながっておらず、応答は返ってこない:
    $ ping 10.10.10.10
    PING 10.10.10.10 (10.10.10.10) 56(84) bytes of data.
    ^C
    --- 10.10.10.10 ping statistics ---
    2 packets transmitted, 0 received, 100% packet loss, time 1017ms
    
  3. "localhost" という名前はつねに自分自身の PC を表すので、つねに応答する (応答時間の短さに注目):
    $ ping localhost
    PING localhost (127.0.0.1) 56(84) bytes of data.
    64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.124 ms
    64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.062 ms
    64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.076 ms
    ^C
    --- localhost ping statistics ---
    4 packets transmitted, 4 received, 0% packet loss, time 3017ms
    rtt min/avg/max/mdev = 0.062/0.088/0.124/0.023 ms
    

DNSのエラー

これまで PC の名前 (ホスト名) として "www.example.com" とか "localhost" という名前を使ってきたが、 実はインターネット上に接続されているコンピュータはすべて IPアドレス によって指定する必要がある。

つまり本当は

$ nc www.example.com 80
ではなく
$ nc 93.184.216.34 80
のようにする必要がある。

ではなぜ www.example.com や localhost といった名前が使えるのか? というと、それは DNS (Domain Name System) という仕組みのおかげである。

注意

DNSによる名前解決は世界中の誰でも利用できるが、 「誰でもDNSで名前解決できる」からといって、 「そのIPアドレスに誰でもアクセス可能である」とは限らない。 実際には、DNSが返す IPアドレスには 特定のネットワークからしかアクセスできないものや、 そもそも何も接続されていない (使われていない) ものもある。

このように DNS はインターネットの基幹をなす仕組みであり、 ほとんどの API やアプリやサービスは DNS に依存している。 DNS が使えなければ、相手の IPアドレスがわからないので、 (たとえケーブルがつながっていたとしても) 通信ができないことが多い。 そのため DNS がうまく動かない場合、 多くのサービスやサイトで大規模な障害が発生する。

演習. ホスト名から IP アドレスを求める

host コマンドを使って、 自分が知っているホスト名の IPアドレスを求めてみよう。

  1. まず、うまくアドレスが返ってくる場合:
    $ host www.example.com
    www.example.com has address 93.184.216.34
    www.example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946
    
  2. 次に「アドレスが存在しない」エラー (NXDOMAIN) が返される場合。 これは DNSがうまく設定されていないか、 あるいはそのような名前が本当に存在しないケースが考えられる:

    $ host ojaflekf.aeiorfja.asldkfj
    Host ojaflekf.aeiorfja.asldkfj not found: 3(NXDOMAIN)
    

DNS の仕組みは複雑なので、ここでの解説は省略する。 万が一 DNS の設定に問題がある場合は、たとえば dig コマンドを使って 原因を調査することができる:

演習. DNSの名前解決プロセスを表示する
$ dig +trace www.example.com
; <<>> DiG 9.18.19 <<>> +trace www.example.com
;; global options: +cmd
.                       27273   IN      NS      j.root-servers.net.
.                       27273   IN      NS      k.root-servers.net.
.                       27273   IN      NS      b.root-servers.net.
...
www.example.com.        86400   IN      A       93.184.216.34
www.example.com.        86400   IN      RRSIG   A 13 3 86400 20231124112747 20231103152748 2182 example.com. ...
example.com.            86400   IN      NS      a.iana-servers.net.
example.com.            86400   IN      NS      b.iana-servers.net.
;; Received 322 bytes from 199.43.133.53#53(b.iana-servers.net) in 106 ms

7. 用語一覧

本講座で出てきた重要な用語一覧。


Yusuke Shinyama