最近、フロントエンドの面接の準備をしていて、この問題は基本的な質問ですので、少しインターネットで調べて整理しました。
全体のプロセスは以下の通りです:
- URL を入力する
- DNS 解決
- TCP 接続を確立する
- HTTP リクエストを送信する
- サーバーが永久的にリダイレクトする
- サーバーがリクエストを処理し、HTTP レスポンスを返す
- ブラウザが HTML を表示する
- リンクが終了する
URL を入力する#
URL(Uniform Resource Locator)は、リソースの場所とアクセス方法を取得するために使用されるものです。
構成は次のとおりです:プロトコル:// ホスト名:ポート番号 / パス /; パラメータ?クエリ #フラグメント
DNS 解決#
DNS(Domain Name System)は、インターネット上でドメイン名と IP アドレスを相互にマッピングするための分散型データベースです。ホスト名に対応する IP アドレスを取得するプロセスをドメイン名解決と呼びます。
DNS 解決のプロセスは、必要なリソースを持つマシンを見つけるためのものです。実際には、翻訳者の役割を果たし、入力されたウェブアドレスを IP アドレスに変換します。
以下は DNS の検索順序の一例です:
- ブラウザのキャッシュ:ブラウザのキャッシュからアクセス履歴を読み取る
- オペレーティングシステムのキャッシュ:システムのメモリ内のキャッシュを検索する
- ホストファイル:ローカルディスクのホストファイルを検索する
- ルーターのキャッシュ:一部のルーターはアクセスしたドメイン名をキャッシュする
- ISP(インターネットサービスプロバイダ)の DNS キャッシュ:ローカルで見つからない場合、ISP は現在のサーバーのキャッシュで検索する
- ルート DNS サーバー:ルートドメインはリクエストを受け取り、どのサーバーが管理しているかを判断し、リクエスト元にトップレベル DNS サーバーの IP を返します。
検索が完了した後、ローカル DNS サーバーはドメイン名の解決サーバーにリクエストを送信し、IP アドレスをコンピュータに返し、対応関係をキャッシュに保存します。
拡張:
DNS のクエリ方法:
- 逐次:ローカル DNS サーバーが他の DNS サーバーにクエリを行います(通常、ドメインのルートサーバーにクエリを行い、次に階層ごとにクエリを行います)。結果はローカル DNS サーバーに返され、それがクライアントに返されます。
- 反復:ローカル DNS サーバーは、ドメイン名を解決できる他の DNS サーバーの IP アドレスをクライアント DNS プログラムに提供し、そのプログラムがこれらの DNS サーバーにクエリを行います(ローカル DNS サーバーがクライアントの DNS クエリに応答できない場合に使用されます)。
DNS の最適化方法:
- DNS キャッシュ
- DNS ロードバランシング
- なぜ必要か:リクエストされるリソースが同じサーバーにある場合、そのサーバーは負荷に耐えられずにクラッシュする可能性があります。
- 原理:ホスト名に複数の IP アドレスを設定し、DNS ファイルの記録された IP アドレスの順序に従って異なる結果を返すことで、アクセスを異なるマシンに誘導します。
TCP 接続を確立する#
IP アドレスを取得したら、3 ウェイハンドシェイクを使用して TCP 接続を確立します。
- 1 回目のハンドシェイク:クライアントはSYN(同期シーケンス番号)パケットをサーバーに送信し、SYN_SENT状態に入り、サーバーの確認を待ちます。
- 2 回目のハンドシェイク:サーバーは SYN パケットを受け取った後、確認を行い、自身も SYN パケットを送信します(SYN+ACK パケット)。サーバーはSYN_RECE状態に入ります。
- 3 回目のハンドシェイク:クライアントはサーバーからの SYN+ACK パケットを受け取り、確認パケット ACK をサーバーに送信します。送信が完了すると、クライアントとサーバーはESTABLISHED状態に入ります。
拡張:
なぜ 3 回のハンドシェイクが必要なのか:無効なリンク要求パケットが突然サーバーに送信されることを防ぐためです。
HTTP リクエストを送信する#
TCP 接続が確立したら、クライアントは HTTP リクエストを発行します。HTTP リクエストには 3 つの部分が含まれます:
- リクエスト行:リクエストメソッド + URL + プロトコル / バージョン
- リクエストヘッダ:リクエストの追加情報とクライアント自体の情報を伝えます
- リクエストボディ:送信するデータ
サーバーが永久的にリダイレクトする#
サーバーはブラウザに 301 永久リダイレクトレスポンスを返します。たとえば、**http://google.com/** にアクセスすると、自動的に **http://www.google.com/** にリダイレクトされます。
目的:
- これにより、www ありとなしのアドレスが同じウェブサイトのランキングに組み込まれ、ウェブサイトの検索リンクのランキングが低下しません。
- 異なるアドレスを使用すると、キャッシュの効果が低下し、1 つのページに複数の名前がある場合、キャッシュに複数回表示される可能性があります。
サーバーがリクエストを処理し、HTTP レスポンスを返す#
バックエンドは固定ポートから TCP パケットを受信すると、TCP を処理し、HTTP プロトコルを解析し、パケットを HTTP リクエストオブジェクトにさらにパッケージ化して上位レイヤーに提供します。
HTTP レスポンスは 4 つの部分で構成されています:
- ステータス行:プロトコルバージョン、ステータスコード、ステータスの説明
- レスポンスヘッダ:キーと値のペアで構成され、1 行に 1 つずつ記載されます(「:」で区切られます)
- 空行:リクエストデータを区切ります
- レスポンスボディ
拡張:
大きなウェブサイトでは、リクエストがリバースプロキシに到達し、同じアプリケーションを複数のサーバーにデプロイし、多数のユーザーリクエストを複数のマシンに分散させます。
つまり、クライアントはまず Nginx にリクエストを送信し、Nginx はアプリケーションサーバーにリクエストを送信し、最終的に結果をクライアントに返します。
ブラウザが HTML を表示する#
ブラウザで HTML を表示するには、解析とレンダリングが同時に行われます。大まかなプロセスは次のとおりです:
- HTML ファイルを解析して DOM ツリーを構築する
- CSS ファイルを解析してレンダリングツリーを構築する
- ブラウザはレイアウトを開始し、レンダリングツリーを画面に描画します
拡張:
リフロー(再レイアウト)とリペイント(再描画)について:
- DOM ノードの各要素はボックスモデルとして存在し、ブラウザはその位置やサイズなどのプロパティを計算するプロセスをリフローと呼びます。
- これらのプロパティが確定した後、ブラウザはコンテンツを描画するプロセスをリペイントと呼びます。
リフローとリペイントはページの初回読み込み時に必ず行われる必要がありますが、これらのプロセスは非常にパフォーマンスを消費するため、できるだけ減らすべきです。
JS の解析と実行メカニズム:
解析中に JS ファイルに遭遇すると、HTML ドキュメントはレンダリングスレッドを一時停止し、JS ファイルの読み込みと解析が完了するのを待ちます(JS は DOM を変更する可能性があるため、例えば document.write)したため、通常、JS コードは HTML の末尾に配置されます。
JS の解析はブラウザの JS 解析エンジンによって行われ、JS は単一のスレッドで実行されますが、I/O の読み書きなどのタスクは時間がかかるため、タスクを実行するためのメカニズムが必要です。同期タスクと非同期タスクがあります。
JS の実行メカニズムは、メインスレッド + タスクキューと見なすことができます。
同期タスクはメインスレッド上のタスクであり、メインスレッド上でスタックを形成します。
非同期タスクはタスクキューに配置されるタスクであり、実行結果が得られるとタスクキューにイベントが配置されます。
このプロセスは繰り返され、イベントループとも呼ばれます。
リンク終了#
現在、ページはリクエストの処理時間を最適化するために、通常は TCP 接続を維持し続けます。TCP 接続が切断されるのは、現在のページが閉じられたときです。
次に、TCP 接続を切断するための 4 回のハンドシェイクが行われます:
- ホストは FIN を送信し、FIN_WAIT_1状態に入ります。
- サーバーは FIN を受け取り、ACK をホストに送信し、確認番号は受信番号 + 1 です。サーバーはCLOSE_WAIT状態に入ります。
- サーバーは FIN パケットを送信してデータ転送を終了し、LAST_ACK状態に入ります。
- ホストは FIN を受け取り、TIME_WAIT状態に入り、その後 ACK をサーバーに送信し、サーバーが自分の ACK パケットを受け取ったことを確認した後、CLOSED状態に入ります。