はじめに
最近「ネットワークはなぜつながるのか 第2版 知っておきたいTCP/IP、LAN、光ファイバの基礎知識」を読んで、ネットワークの全体像を復習することが出来ました。
せっかくですので、今回インプットした内容の定着を図りたく、記事にしてみることにしました。
概要
本書は「WEBサーバにリクエストを出し、そのレスポンスがブラウザで表示されるまでのパケットの道筋」にという流れに沿って書かれています。
その中でも特に興味深かった箇所が
- クライアント端末がパケットを送り出す仕組み
- WEBサーバがリクエストを受信し、レスポンスを返し、クライアント側で表示される仕組み
でしたので、本書から上記をピックアップしてまとめて行きたいと思います。
※読んで個人的に面白かった箇所をすっぱ抜いておりますので自由度の高い書き方になっております。ご容赦ください。
クライアント端末がパケットを送り出す仕組み
クライアント側のパケットの動きとしては
①ユーザがアプリケーション(Chromeなどのブラウザ)経由でWEBサイトのURLをクリックする
➁アプリケーションがリクエスト・メッセージを作成する
③アプリケーションがSocketライブラリから必要なプログラムを呼び出す
④リクエスト・メッセージに、プロトコルスタックが各役割に応じたヘッダを付与する
⑤LANアダプタがデータを電気信号に変換し、電気信号を送り出す。
上記のような流れになります。
図にまとめると以下のようになりますので、この図に沿って解説をしていきます。
※LANドライバについては、あくまでプロトコルスタックとLANアダプタの仲介役であるので、説明は省略します。
パケットの流れを説明する前に、そもそもの用語について解説します。
ブラウザ
ChromeやEdgeなど、日ごろユーザーブラウジングの際使用するアプリケーションとなります。
主な役割としては
- ユーザーが入力したURLを解読
- HTTPリクエストを作成
※以下HTTPリクエストのサンプルです。
※画像があったり複数のファイルを呼び出す際には、その都度リクエストを送ることになります。
GET /api/v1/magical-items?category=wands&color=blue HTTP/1.1
Host: magicstore.example.com
User-Agent: MagicBrowser/1.0
Accept: application/json
Authorization: Bearer abc123def456ghi789
X-Custom-Header: MyHeaderValue
Connection: keep-alive
- Socketライブラリの呼び出し
ブラウザは必要に応じてOSに対して様々な依頼を出します。
その一つが「Socketライブラリの呼び出し」です。例えば、名前解決をするときは、以下のようにしてDNSリゾルバを呼び出します。
<メモリ領域>=gethostbyname("URL")
- レスポンス・メッセージの解釈
リクエスト・メッセージに対する応答メッセージの解釈も、ブラウザの役割です。
内容に応じて、再度WEBサーバに対してリクエスト・メッセージを送信したり、あるいはOSに依頼して画面表示動作を実行したりします。
Socketライブラリ
OSの中にいるプログラムの部品集みたいなもので、必要に応じてアプリケーションから呼び出されます。
あくまで部品集にしか過ぎないので、実際に実行するのは後述のプロトコルスタックになり、終了したら制御がアプリケーションに戻ります。
以下がクライアント側での主要なプログラムとなります。
- gethostbyname("URL")
- seocket()
ちなみにソケットはWindows であれば「netstat」コマンドで確認することが出来ます。
※以下架空のIPで実施したnetstatの出力結果です。
C:\Users\user>netstat
Active Connections
Proto Local Address Foreign Address State
TCP 192.168.1.100:139 0.0.0.0:0 LISTENING
TCP 192.168.1.100:443 203.0.113.45:51234 ESTABLISHED
TCP 192.168.1.100:445 0.0.0.0:0 LISTENING
TCP 192.168.1.100:3389 198.51.100.23:49680 ESTABLISHED
TCP 192.168.1.100:5050 203.0.113.75:52345 TIME_WAIT
UDP 192.168.1.100:137 *:*
UDP 192.168.1.100:138 *:*
C:\Users\user>
- connect()
- write()
- read()
- close()
プロトコル・スタック
ブラウザは、リクエストメッセージを作成することは出来ても、ネットワークに対してデータを送受信する機能は持っていません。
Socketライブラリのプログラムを、アプリケーションが順番に呼び出すことでデータがネットワークに送信されます。
プロトコルスタックにはTCP担当とIP担当があり、リクエストメッセージをサーバに送る時には
TCP→IPの順番でデータが送信されます。
パケットの流れ
今まで説明してきた内容を踏まえて、パケットの流れを整理します。
ここではHTTPリクエストを送り、HTTPレスポンスを受け取るというシナリオで流れを見ていきましょう。
図にすると以下のようになり、アプリケーションが必要なSocketライブラリを呼び出して処理が進んでいくことがわかります。
名前解決
アプリケーションが「gethostbyname」を呼び出し、DNSサーバに対してWEBサーバのIPアドレスを問い合わせします。レスポンスメッセージに含まれているIPアドレスは、アプリケーションが指定したメモリに格納されます。
名前解決の順序ですが、最寄りDNSに必要なキャッシュがない場合は以下の順番で問い合わせます。
①最寄りのDNS
※すべてのDNSサーバにはルートドメインのDNSサーバのIPアドレスが登録されています
➁ルートドメインを登録しているDNS
③下位のDNSに下る
※リクエストを送るにあたって、プロトコルスタックやLANアダプタが関与しますが、こちらについては後述します。
ソケットの作成
アプリケーションが「socket」を呼び出し、ソケットを作成します。
プロトコル・スタックはメモリ領域を確保します。そしてソケットが作成し終わったら、ディスクリプタをアプリケーションに通知します。これによりアプリケーションが他のプログラムをSocketライブラリから呼び出す際に、ディスクリプタを利用できます。
※ディスクリプタはソケットの識別子のようなものです。
接続動作
ソケットが作成し終わったら、接続動作に入ります。
アプリケーションがconnectを呼び出す際に、プロトコル・スタックに送信先のIPアドレスとポート番号が伝わります。プロトコル・スタックTCP担当は、SYNコントロールビットを1のTCPヘッダを作成し、プロトコル・スタックIP担当に送信を依頼します。これが通称3wayhandshakeの開始で、図のように3回のやり取りによって接続が確立されます。
この3wayhandshakeについては、以下の記事を参考にすると非常に分かりやすいかと思います。
この時、プロトコル・スタックIP担当によってIPヘッダとMACヘッダが付与され、サーバ側に向かう通信はLANアダプタによって電気信号に姿を変え、ケーブルに送り出されます。
データの送信
3wayhandshakeによる接続確認が終了後、connectからアプリケーションに制御が戻ったら、アプリケーションはwriteを呼び出します。ここで送信データがプロトコル・スタックに伝わります。
送信時は、データの大きさに応じて分割がされますが、詳細は以下をみるとわかりやすいかと思います。
パケットが分割された場合でも、データの抜け漏れがないかチェックする仕組みが必要になります。
そこで「シークエンス番号」や「確認応答番号」といった概念が登場します。
-
シークエンス番号
データが分割された際に、その断片が通信開始から初めて何バイト目に相当するかを示す番号です。TCPヘッダーに記載されます。 -
確認応答番号
受信側が、何バイト目まで受信したかを示す番号です。
こちらも同様にTCPヘッダーに記載されます。
要約すると、
送信側「○○バイト目から始まるデータを××バイト送ります」
※「○○」がシークエンス番号に該当
受信側「△△バイト目まで受け取りました」
※△△が確認応答番号に該当
上記のようなやり取りを繰り返すことによって、送受信時でもデータの抜け漏れを無くしています。
補足ですが、シークエンス番号は乱数を基に算出した数で始まります。接続動作時にSYNを1にしておくる場面があったかと思いますが、その際にシークエンス番号の初期値をセットすることになっています。
また「タイムアウト値」や「ウィンドウ制御」といった概念がありますが、細かくなりすぎますので説明を省略します。
データの受信
データを送り終わったら、アプリケーションはreadを呼び出して受信バッファにあるデータをアプリケーションに渡そうとします。しかし、データが送り終わった直後だと、まだレスポンスがありません。
そのため本作業は一時的に棚上げされます。
サーバーからのレスポンス・メッセージのTCPヘッダーには、確認応答番号が含まれています。
プロトコル・スタックによる
- TCPヘッダの内容を調べて、データが抜けてないかどうかを調べる
- 問題なければACKを送り返す
上記を待った後で、受信データをアプリケーションに渡します。
切断動作
サーバ側から切断動作に入る場合は、以下の順番で切断動作がなされます。
- サーバ側のアプリケーションがcloseを呼び出す
- TCPヘッダのコントロールビット「FIN」を1にしたパケットをクライアントに送信する
- クライアント側がそのパケットを受信後、ACK番号をサーバに送り返す
- クライアント側アプリケーションが、サーバーからデータを全て受信し終わったことを確認する
※この動きはreadが呼び出されることによって実施されます。readは受信バッファにあるデータをアプリケーションに伝える仕事をしますが、ここではアプリケーションに対して受信が終了したことを通知します。 - クライアント側アプリケーションがcloseを呼び出す
- TCPヘッダのコントロールビット「FIN」を1にしたパケットをサーバーに送信する
- しばらくしてソケットが削除される
以上がクライアントで起こるパケットを送り出すまでの動きです。
送り出されたパケットは、LANを経由し最寄りのルータにたどり着きます。その後はアクセス回線を経由し、プロバイダが管理するネットワークに入り、WEBサーバー側のLANにたどり着き、最終的にWEBサーバ到着します。
このあたりの動きもいつか記事にできたらと思います
WEBサーバがリクエストを受信し、レスポンスを返す仕組み
クライアントとの大きな違い
では次にWEBサーバがリクエストを受信する流れを整理します。
前提として、WEBサーバは複数のクライアントと同時にやり取りをする必要があります。そのため、アプリケーションは以下のようなプログラム作成し、適切なタイミングで実行します。
-
接続を待ち受ける部分
→初期化動作が終わったところで、この部分が実行されます。 -
クライアントとやり取りをする部分
→クライアントが接続されたタイミングで実行されます。
上記図のように、クライアントが接続してくる都度、アプリケーションは新しいプログラムを実行することになります。
アプリケーション内部の動き
では実際にどのようなSocketライブラリのプログラムが読みだされているのか、確認していきましょう。
-
ソケット作成
socketを呼び出し、ソケットを作成します。 -
ソケットに接続情報を記録
bindを呼び出し、作成したソケットに対してポート番号を記録させます。 -
ソケットを接続待ちの状態に変更
listenを呼び出し、作成したソケットに対して、接続待ちの状態であるという制御情報を記録します。 -
接続の受け付け
acceptを呼び出して、接続を受け付けます。
この部分まで、サーバが起動してから実行されます。そのためクライアントから接続があるまでは、アプリケーションはaccept実行後、休止状態になります。実際に接続パケットが届いたら応答パケットを送り返して、もともとのソケットをコピーして新しいソケットを作成します。 -
クライアントとやり取りをする部分の起動
新しいソケットとクライアントが接続出来たら、クライアントとやり取りする部分を起動し、接続したいソケット情報を渡します。
上記を補足すると、「接続の受け付け」で新しいソケットを作成した際に、同じポート番号を割り当てます。すると同じポート番号をもつソケットが複数存在することになります。そのためソケットの特定には、以下の要素が使用されます。
- クライアントのIPアドレス
- クライアント側のポート番号
- サーバ側のIPアドレス
- サーバ側のポート番号
電気信号からデジタルデータに変換される流れ
まず電気信号はLANアダプタが受付け、以下を実施します。
- デジタルデータへの変換
- FCSによるエラーのチェック
- MACアドレスの確認
- LANアダプタのバッファメモリに受信パケットを格納
上記の後、LANアダプタはCPUにパケットが受信されたことを通知し、その後CPUはOS(TCP/IPプロトコル・スタック)に制御を渡します。
この際、MACヘッダーのタイプ・フィールド(IPv4なのか、IPv4なのか、あるいはARPなのか・・・など)を見て、呼び出すプロトコル・スタックを判断します。
※わざわざLANアダプタ→CPU→OSという回り道をするのは、あくまでOSもメモリにロードされたプログラムの一つであり、ハードウェアからの通知はCPUを経由されて行われるからです。
プロトコル・スタックにパケットが渡された後の流れ
プロトコル・スタックの復習をします。
図のように、TCP担当とIP担当が存在し、今回の例ではまずIP担当部分が呼び出されます。
LANアダプタがMACヘッダーをチェックしたように、IP担当部分はIPヘッダーの以下の点をチェックします。
- パケットが自分宛かどうか
- フラグメンテーションがあるかどうか
上記がチェックし終わったら、IPヘッダーのプロトコル番号欄を調べ、どこにパケットを渡すか確認します。
今回はUDPではなく、TCP担当にパケットを渡すとして、説明を続けます。
TCP担当部分の動きは、受信したデータが接続動作なのかどうかで、異なります。
- TCPヘッダーのSYNコントロールビットが1だった場合
TCPヘッダーの宛先ポート番号を調べ、該当ポート番号が割り当てられたソケットがあることを確認します。
もしなかった場合は、エラー通知をクライアントに送ります。
該当する接続待ちソケットがあれば、そのソケットをコピーし、以下のようなクライアントに関する必要情報を記録します。
- 送信元IPアドレスやポート番号
- シークエンス番号の初期値
- ウィンドウの値
同時に、メモリー領域を確保した後、以下のようなサーバに関する項目を記載したTCPヘッダーを作成し、IP担当部分に依頼して、クライアントに送り返します。
- ACK番号
- シークエンス番号
- ウィンドウの値
- データパケットを受信した場合
まずは、届いたパケットの送信元IPアドレスや、ポート番号を確認し、接続するソケットを判断します。
その後シークエンス番号を参考に、パケットの抜け落ちがないか確認した後、受信バッファへのパケットを保存します。
そして、適切なACK番号(シークエンス番号とデータ断片の長さから計算)を割り当てて、IP担当部分に依頼して、クライアントに送り返します。
アプリケーションは、通常この時点でreadを呼び出して、データの到着を待っている形になっているので、データがアプリケーションにわたってプロトコル・スタックの役割としてはいったん終了になります。
「アプリケーションがHTTPリクエストをどのように処理するか」の説明の前に、先に切断動作について説明します。
※ここではサーバから切断動作を開始するケースで説明します。
- サーバ側のアプリケーションがcloseを呼び出す
- サーバ側TCP担当がFINコントロールビットを1にしたTCPヘッダーを作成し、IP担当部分に送信依頼をする
- クライアントに上記が届いたら、クライアントはACK番号を送り返す。
- クライアント側アプリケーションがcloseを呼びだし、FINが1になったTCPヘッダーをサーバに送る
- サーバがACK番号を送り返して、切断動作は終了
WEBサーバ側リクエスト・メッセージを解釈して要求に答える流れ
サーバ側は、リクエスト・メッセージに含まれているURIに記載されているパスを読み込みます。
セキュリティの観点で、WEBサーバとして公開するディレクトリは仮想的なものであり、ファイルを読み込むときには変換がされます。
仮にURIのファイルの中身がHTMLや画像の場合は、そのままレスポンス・メッセージとして返します。
ただ、リクエスト・メッセージによっては、WEBサーバからプログラムを呼び出す必要がある場合があります。その場合、リクエスト・メッセージの中にはプログラムに渡すデータが含まれています。
以下の例では、「POST」というメソッドが使用されており、WEBのフォームに「test」と入力して送信した時のリクエスト・メッセージです。
POST /your-endpoint-url HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Content-Type: application/x-www-form-urlencoded
Content-Length: 9
field=test
上記のようなリクエスト・メッセージを受け取ったアプリケーションは、OSに該当のプログラムを起動するように依頼します。
そして必要なデータ(上記の場合は「test」)を取り出して渡します。
その後プログラムが出力したデータがアプリケーションに返ってくるので、結果をクライアントに返します。
クライアントにレスポンス・メッセージを送り返す時は、アプリケーションがwriteを呼び出し、その後プロトコル・スタックに送信を依頼します。
※上記の説明は、サーバー側にアクセス制御が行われていない場合となります。アクセス制御をする場合は
- クライアントのIPアドレス
- クライアントのドメイン名
- ユーザー名とパスワード
などの要素でアクセスを制御します。
クライアントがレスポンス・メッセージを受け取り画面に表示するまで
以降は、クライアントがレスポンス・メッセージを受信した際の動きを記載します。
LANアダプタが信号からデジタル・データに変換し、CPUに通知がされた後、プロトコル・スタックのチェックを終えて、アプリケーションにメッセージが渡ります。
その後アプリケーションは「Content-Type」や「Content-Encoding」を確認し、データの中身を調べ必要に応じてプログラムを呼び出すようにOSに依頼します。
※あくまで実際に画面表示動作をするのはOSの役割です。
これにより、ようやくクライアント側に必要な情報が表示されます。
終わりに
本記事では、「ネットワークはなぜつながるのか 第2版 知っておきたいTCP/IP、LAN、光ファイバの基礎知識」を読んで個人的面白かった箇所をまとめてみました。
最も悩んだのが、何を記事にまとめて、何を省略するかということで、改めて見返すと「網羅的に説明できてない」ことが一目瞭然の記事になってしまいました。
とはいえ、個人的には本書を読めて非常にハッピーでしたので、また機会を見て今回紹介できなかった部分も記事に出来ればと思います。