透過 Chrome 深入理解瀏覽器導航過程

袋鼠雲數棧前端 發表於 2021-10-12
Chrome

網路的導航,是從輸入 url 到最終獲取到檔案的過程。其中牽扯到瀏覽器架構、作業系統、網路等一系列知識。本文將從各個角度詳細論述這一過程,涉及廣度與深度。如果您是已經有一定基礎的同學,那麼本文可以快速帶你係統化整理碎片化知識。

導航篇

本小節,我們將以普通請求作為抓手,跟隨請求資料包漫遊整個 OSI 模型,本節目錄:

解析 URI

當我們在位址列輸入需要請求的網站地址,如:晨風 並按下回車,
Chrome 首先會解析內容,判斷這是 URL 還是搜尋內容,若是搜尋內容則自動 URL 編碼並拼接為預設搜尋引擎的 params

如果是 URI ,如:test.com,則處理 URI,新增 http 並預設訪問 80 埠號。

file

Chrome 層面,如果你的位址列原本就有展示頁面,那麼進行上述操作後,會觸發當前頁面的 beforeunloadunload 事件。同時瀏覽器標籤進入 loading 圖示狀態,新頁面有兩個重要的時間節點,在渲染篇會詳細介紹:

  • interactive:它表示瀏覽器已經完成了 HTML parserRecalculate StyleLayout TreeRender Treedraw list 等工作。
  • complete: 它表示瀏覽器已經完成頁面渲染,這會替換掉本視窗原本的點陣圖,顯示最新的介面。在 interactivecomplete 之間,就是渲染程式中的合成執行緒的工作位置,Chrome 渲染程式基於 skia 進行 2D 介面元素的繪製。

早些時候一些網站會在 URI 中直接制定路徑和具體的字尾的檔案,如:https://www.test.com/home/index.html。但是這所帶來的諸如非法訪問等安全問題與網際網路業務需求的爆發式增長,人們對 Web 的安全性與效率有了更高的要求,因此引入代理伺服器滿足 保證安全負載均衡快取代理 等需求。現代 Web 幾乎都採用代理伺服器以隱藏真實的資源位置。

構建請求

通過 URI Check 後,Chrome 需要為它建立 get 請求,在此之前先介紹一下 Chrome 的架構組成。

Chrome 目前採用的是 SOA 架構,主要特點是將應用程式的不同的 Service 進行拆分,並通過這些服務之間定義良好的介面和協議聯絡起來。常用的程式如下:

  • 瀏覽器主程式:負責頁面展示,使用者互動,子程式管理等功能
  • 渲染程式:每個選項卡都有自己的渲染程式,無關乎是否為 same-site 站點SandBox 執行環境,處理 HTMLCSSJavaScript。同時 V8Blink 也都執行在該程式中。
  • 外掛程式:負責外掛執行,根據外掛的功能決定是否執行在 Sandbox 環境
  • GPU 程式:處理一些特殊的 CSS 效果
  • NetWork Service:處理網路資源載入,請求響應,校驗 CORS。
  • Storage Service:處理對 localStoragesessionStoragecookieIndexed DB 儲存的控制。
  • Audio Service:處理音視訊 Buffer 的音量播放等操作
  • V8 PAC tool:利用 V8 解析 PAC 檔案,幹一些你懂得的事 😁。

從上文得知 Chrome 主程式需要通過 IPC 把構建請求的任務委託給 NetWork Service 負責此任務。

NetWork Service 接受任務後,建立了 get 請求,其中請求行由 請求方法 + 請求路徑 + HTTP 版本號 組成;請求頭資訊由 Chrome 內建提供。

file

在 HTTP 2.x 標準中引入了 HpackStream,其中 Hpack 主要的目的是壓縮請求報文頭資訊以減少每次連結傳送的冗餘資料。 它會將報文頭資訊整合成一張 Hash Table,並使用 Huffman 編碼壓縮文字內容。並且請求行也被取消,其內容置入 Hash Table 首部並以 : 開頭,以此區分請求行與請求頭資訊。Stream 的作用我們稍後介紹

查詢強快取

NetWork Service 會委託 Storage Service 依次在 service work cachememory cachedisk cachepush cache(HTTP2 Stream)中尋找對應的 URI 是否有可用的強快取,如果存在強快取,則直接使用快取進入瀏覽器解析環節,否則進入 DNS 解析。

為了方便同學們學習和驗證,我把非 memory cache 的 cache 資源在 MacOS 的位置統計如下:

  • Service Work Cache:/Users/YOUR_NAME/Library/Application Support/Google/Chrome/Default/Service Worker/[CacheStorage || ScriptCache]
  • Disk Cache:/Users/YOUR_NAME/Library/Application Support/Google/Chrome/Default/Application Cache/Cache

由於 Chrome 取消了通過 chrome://cache 進行訪問,因此檢視這類問題時需要自行安裝反編譯工具檢視。

一般情況下大檔案會預設存放在 disk cache 中,小檔案存入 memory cache。但當記憶體使用率較高時,需要緩解使用壓力會優先放入 disk cache

HTTP 2 提供了多路複用,頭部壓縮、Service Push。其中 Service Push 是唯一需要手動實現的功能,Service Push 可在某次 Stream 中返回使用者端還沒主動請求,但是相關的資料,以節約報文上不必要的開銷。

DNS 解析

若強快取不存在或過期時,NetWork Service 繼續將報文傳送至接收端。這需要 OS 的配合,首先需要將報文委託給 OS 至協議棧,但 OS 無法識別報文對應的 domain ,因此無法提供相應幫助。我們必須提供 IP 地址。將制定域名轉換成 IP 的工作是由 DNS 伺服器提供。

域名的誕生也是為了符合人們的習慣性記憶,沒有人喜歡記憶無意義的 IP 地址。於是便有了 DNS 服務賦能 IP 對應的 Domain 以方便記憶。

DNS 層級

由於域名系統是外國人發明的,因此 DNS 的層級劃分是從右往左根據 **.** 進行切分,它就像英文人名一樣,根域 / 姓氏 取域名最末尾的部分,不符合國人記憶習慣。

根據層級 DNS 伺服器分為:

  • 根域 DNS 伺服器:不儲存具體的域名資訊,但它是通向所有頂級域 DNS 伺服器的總入口
  • 頂級域 DNS 伺服器:代表不同的域名字尾伺服器,如 cncomtech 等。同樣不儲存具體的域名資訊,是通往對應字尾權威 DNS 伺服器的總入口
  • 權威 DNS 伺服器:正如其名,代表著對應 Domain 對映 IP 的權威。它是儲存對映關係的真實伺服器。

file

從上圖可知,DNS 伺服器之間有著類似 trie 樹的結構,樹的每一層的資訊都是完整域名的一部分且非葉子節點的資訊均是沒有幫助的。而葉子節點被稱為權威伺服器,是 IPDomain 對映關係儲存的實際位置。根域 DNS 伺服器的資訊儲存在網際網路中所有的 DNS 伺服器中,正因如此,客戶端只需要訪問到任意 DNS 伺服器就可以順著它找到根域伺服器,從而獲取目標 IP。

Chrome 從 83 版本開始正式開始了 DOH 即 DNS-over-HTTPS,主要目的是防止原本的 DNS 請求因為是 HTTP 明文傳輸導致容易被中間人篡改,因此 DOH 就是批著 TLS 的 DNS 請求。

Hosts

正如 JavaScriptPromise 支援 thenableinstanceof 支援 Symbol.hasInstanceJSON.stringify 支援 toJSON 等等都會開設一個定製化行為的入口。

域名解析也存在本地定製化入口 Hosts。它是一個本地的關聯 “資料庫”,將 DomainIP 地址相對應。解析優先順序大於 DNS 服務。

DNS 解析流程

筆者以訪問 http://www.test.com 為例,DNS 的解析流程如下:

  • 檢視 hosts 是否儲存目標 domainIP 地址的對映關係,若找到則直接返回給客戶端。
  • hosts 不存在對應 domain,客戶端建立 DNS 請求,問詢本地 DNS 伺服器 Domain 對應的 IP 地址。
  • 本地 DNS 伺服器收到請求後,首先檢視 DNS 快取能否找到 domain 對應的 IP 地址,若找到則直接返回給客戶端。若 DNS 快取中不存在,則找到自身記錄的根域 DNS 地址併發起請求問詢根域伺服器 Domain 對應的 IP 地址。
  • 根域伺服器不儲存具體的資料,但是指明瞭我們接下來詢問的目標:對應 com 的頂級域名伺服器地址。
  • 本地 DNS 伺服器收到根域的回應後,繼續問詢 com 的頂級域名服務。
  • 頂級域名伺服器同理會返回相對應 test.com 的權威伺服器地址。
  • 本地伺服器繼續問詢權威伺服器,它是域名解析結果的原出處,也是最後一次問詢。
  • 權威 DNS 伺服器返回域名對應的 IP 地址給客戶端。
  • 本地 DNS 伺服器快取結果。將 IP 發給 OS
  • OS 返回 IPChrome NetWork Service

這下 NetWork Service 擁有了綠卡,已經萬事俱備。終於可通過 socket library 將資料委託給 OS 以進入協議棧啦。同時也標誌著即將離開 OSI 應用層。

協議棧

請求資料包在 OS 的幫助下進入協議棧。工作在應用層與傳輸層中間的協議棧會處理對應 H2HpackStream,如果 domain 使用了 TLS / SSL 協議,那麼 OS 會從本地加密套件列表中選取加密套件,並將資訊新增至資料包。

傳輸層

至此資料包來到協議簇上層,它表示工作在傳輸層和網路層相關的協議總稱。協議簇分為上下兩個部分,分別承擔不同的工作且上下層關係有一定的規則,上層完成部分工作後會委託下層繼續執行。在上層協議簇中最先映入眼簾的便是負責資料包收發的 TCP / UDP

TCP

TCP 是面向一對一連結,可靠有狀態且基於位元組流的協議。在 HTTP 傳輸資料之前,首先需要 TCP 建立連線,TCP 連線的建立,通常稱為三次握手。

在深入介紹 TCP 前,我們得先了解 MTU,它是一個網路包的最大長度,在乙太網中一般為 1500 位元組。而我們的 HTTP 資料表都昌都很有可能會大於 1500,所以要對超出的內容進行切片傳送,TCP 會對報文進行切分並新增一些資訊以確保每個資料包能順利到達接收端。TCP 資料的最大長度為 MSS,它通過 MTU - TCP head - IP head 計算而來。至此我們介紹下 TCP Head 具體新增的資訊。

file

源埠、目標埠

首先是一組埠號,如果沒有它們,資料包到端後不知道自己是屬於哪個埠應用的資料。
同時,我們也通過源 IP、源埠、目標 IP 和目標埠組成唯一的標識。

這時候可能有同學提出疑問,那麼瀏覽器開啟多個頁籤時,如果訪問的域名和埠也一樣,資料如何對應正確的標籤?筆者推斷 Chrome 可能通過 TCP 的 timestamp 或 ISN 進行識別,若有同學能提供準確的答案歡迎指出。

Sequence Number

簡稱 seq,它代表本報文段第一個位元組的序列號,序列號是一個長為 4 個位元組,也就是能夠表示 32 位的無符號整數。如果到達最大值了後就迴圈到 0。它主要有以下幾個作用

  • 確保端具有傳送功能的標誌。
  • 初次傳送 SYN 報文時交換 ISN。
  • 確保被切分的資料包以正確的順序組裝。

ISN 即 Initial Sequence Number,通過三次握手中的前兩次握手進行交換,其目的是防止不法分子得知 ISN 後偽造 IP 和 Port 通過 TCP 標誌位對連結進行非法攻擊。由於現在的 ISN 並不是一個固定的值,而是每 4 ms 加一,溢位則回到 0。從而大大提高了攻擊者猜測 ISN 的難度。

Acknowledgment Number

簡稱 ack,和 seq 一樣,佔有 4 個位元組,具體表示小於此位元組的內容均已收到。它主要有以下幾個作用

  • 確保端具有接收能力
  • 告知傳送端期望下一次傳送的資料起始位置
標記位

根據 TCP 報文處理資訊的類別不同,需要給予一定的標識,這就是標記位。
常見的標記位有 SYNACKFINRSTPSH。這方面比較基礎,不清楚的同學可以結合三次握手去詳細瞭解。

視窗大小

它賦能 TCP 做流量控制,通訊雙方各宣告一個視窗(快取大小),標識自己當前能夠的處理能力。這也被稱為初始化視窗。
除了用滑動視窗做流量控制以外,TCP 還會通過擁塞視窗做擁塞控制,通過初始化的擁塞視窗採取慢啟動、快速重傳和快速恢復、擁塞避免等能力。

校驗和

佔用兩個位元組,防止傳輸過程中資料包有損壞,如果遇到校驗和有差錯的報文,TCP 則直接丟棄,通過使返回的 ack 值保持不變以提醒傳送端需要重傳。

緊急指標

這是為了應對一些應用程式在某些緊急情況下(如在某些連線中進行強制中斷)
要求在接收方在沒有處理完資料之前就能夠傳送一些緊急資料。

選項

這是 TCP 中的可選項,其中比較重要的是

  • TimeStamp: TCP 時間戳,解決 RTT 錯亂與序列號迴繞
  • MSS:前文提過,通過 MTU - TCP head - IP head 計算而來。

回到三次握手,所謂的建立連線,只是雙方計算機裡維護一個狀態機,在連線建立的過程中,雙方的狀態從 close → established。通過三次握手的 SYNACK 傳遞確保雙方的傳送接收能力。

file

在 Linux 中我們可以通過 netstat -napt 來檢視 TCP 連結狀態:

tcp        0      0 0.0.0.0:5440            0.0.0.0:*               LISTEN      9138/java
tcp     1070      0 199.161.10.251:9020      199.161.10.251:34512     CLOSE_WAIT  4122/java
tcp        1      0 199.161.10.251:60254     199.161.100.195:38399    CLOSE_WAIT  7377/java
tcp     1076      0 199.161.10.251:9020      199.161.10.251:34540     CLOSE_WAIT  4122/java
tcp      416      0 199.161.10.251:9020      199.161.10.251:39166     CLOSE_WAIT  4122/java
tcp        0      0 199.161.10.251:36956     199.161.10.116:22        ESTABLISHED 7377/java

在實際的網路環境中,此時資料包傳輸被阻塞,先和對端完成三次握手,之後才繼續傳送資料。這也是人們在 HTTP3.0 前,稱 HTTP 是基於 TCP 的主要原因,同時從此處可以看出** TCP 的隊頭阻塞**是個不可避免的問題。

TCP 還提供了 keep-alive 功能,但是非常雞肋。

UDP

UCP 是面向無連線,一對多傳送且無狀態的協議。

由於 TCP 的先入為主且其可靠性經受住了歷史的考驗,讓我們很容易相信它會一直保持 Web 端傳輸層的主導地位,然而 Google 團隊的開創能力,也再一次讓筆者大開眼界。HTTP 3.0 標準將拋棄 TCP。 UDP 成功獲得主導地位。

主要的原因想必大家早已略有耳聞,TCP 連結必須經歷三次握手,即便你使用了 TFO (TCP Fast Open) 也一樣。如果需要提高資料互動的安全性,既增加傳輸層安全協議(TLS),在確保安全的 Session Ticket 優化方案下也需要增加 1 RTT。我們不考慮 PSK,因為它不安全。總之, TCP 協議連線建立的成本相對較高,由於 TCP 是在作業系統核心和中介軟體韌體(上文所提的協議棧)中實現的,因此對 TCP 進行重大更改幾乎是不可能的。

而 UDP 協議是無連線協議。客戶端發出 UDP 資料包後,只能“假設”這個資料包已經被服務端接收。好處是在傳輸層無需對資料包進行校驗,一般用於網路遊戲、流媒體資料的一些傳輸。與之相對的,如果需要確保資料傳輸的可靠性,應用層協議需要自己對包傳輸情況進行確認。此協議就是 QUIC

QUIC 協議是基於 UDP 的低時延的網際網路傳輸層協議。 HTTP 2.0 解決了由 HTTP 引起的隊頭阻塞問題,但更深層次的 TCP 隊頭阻塞問題無法避免,QUIC 基於 UDP 協議,因此徹底解決了所有隊頭阻塞問題。

QUIC 協議根據連線的伺服器是新的還是已知的,可在 1-2 個 RTT 內完成連線的建立(包括支援 TLS),這具備很高的誘惑力。

QUIC 雖然有諸多優勢,但目前仍未達到大量普及的階段,並且目前部分路由會封殺 QUIC 所在的 443 埠,UDP 包過多讓服務商誤以為是攻擊、防火牆對 QUIC 的支援等均未到位。讓我們一起期待 QUIC 協議規範能夠成為終稿並實現推廣的那天。

本文主要還是以目前主流的 TCP 為主,經過 TCP 包頭後,當前資料包如下:

file

TCP / IP 協議簇下層

在傳輸層執行連線、收發、斷開等各階段操作都需要委託 IP 協議將資料包封裝成網路包傳送給通訊物件。下面我們來看看 IP 報文頭部的格式

file

其中最重要的是源 IP 與目標 IP。

  • 源 IP 即當前客戶端的 IP 地址
  • 目標 IP 為 DNS 域名解析得到的接收端伺服器  IP

其次是 IP Header 中的協議號,表示傳輸層使用的協議。以十六進位制表示。例如 06 表示的是 TCP。經過 IP Header 包裝後,如下:

file

路由表

通過 IP Head,我們知曉了接收資料的目標 IP,但我們不能確定這個 IP  距離我們地址位置有多遠,很多時候或許我們無法直接傳送到對端,而是要在閘道器中進行數次中轉,控制此過程就是根據路由表的規則。

在 Linux 系統可以根據 route -n 檢視當前系統的路由表。

Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         199.161.100.1    0.0.0.0         UG    100    0        0 eth0
199.254.169.254 199.161.100.153  255.255.255.255 UGH   100    0        0 eth0
199.161.100.0    0.0.0.0         255.255.252.0   U     100    0        0 eth0

此步也是閘道器是否介入連結過程的關鍵分歧點。來看看路由表是如何工作的。

  • 首先根據路由表 list 依次取出每一條資訊
  • 根據每條資訊中的子網掩碼(Genmask)與接收方的目標 IP 進行與運算,如果結果和 Destination 匹配,說明與我們通訊的對端處在同一乙太網中,接下去的傳送不需要走閘道器。並確定以當前的 IP 作為 IP 包頭地址。
  • 若以上匹配都失敗了,那麼會匹配到預設閘道器,一般這就是路由器的 IP,最後網路包會轉發給路由器,讓路由器幫忙傳送。

ARP

生成 IP 包頭後,網路包還需要加上 MAC 包頭,IP 的誕生是為了更加方便的管理計算機在各類乙太網中的身份。而連入所有網路的每一個計算機都會有網路卡介面,每一個網路卡都會有一個唯一的地址,這個地址就叫做 MAC 地址。計算機之間的資料傳送,就是通過 MAC 地址來唯一尋找、傳送的。MAC 包頭的結構如下:

file

其中傳送方的 MAC 非常容易確認,因為 MAC  在網路卡生產過程中已經寫入 ROM 中,直接讀取此值寫入 MAC 頭部即可。
接收端的 MAC 地址相對複雜一些,當前我們已經知曉了接收方的 IP,通過子網掩碼,我們能把接收端分為兩類

  • 處於同一子網的鄰居
  • 處於外部子網的通訊物件,我們交給居委會大媽(閘道器)去通訊

可以看得出來,不論通訊物件是否是鄰居,我們首次傳送的物件都是同一子網的,也許是鄰居伺服器也許是閘道器。因此我們使用廣播進行問詢目標的 MAC 地址。

廣播

ARP 協議會在乙太網中以廣播的形式,對乙太網所有的裝置問話路由表匹配的目標 IP 地址對應的 MAC 地址。

就像操場的喊話,所有人都可以聽見,但如果喊話物件不是自己,不再回應就是。被喊話的物件聽見後,以 MAC 地址作為回應。

file

ARP 快取

正如大部分服務一樣,ARP 也有自己的快取系統,以空間換時間提高效率。獲取到 MAC 地址後,OS 會把本次查詢結果放到一塊叫做 ARP 快取的記憶體空間留著以後用,不過快取的時間就幾分鐘。
也就是說,在構建 MAC 包頭時:

  • 先查詢 ARP 快取,如果其中已經儲存了對方的 MAC 地址,就不需要傳送 ARP 廣播查詢,直接使用 ARP 快取中的地址。
  • 而當 ARP 快取中不存在對方 MAC 地址時,則傳送 ARP 廣播查詢。

linux 中可以使用 arp -a 檢視 ARP 快取的內容

gateway (199.161.100.1) at 79:2c:29:11:0a:32 [ether] on eth0
? (199.161.100.251) at ff:91:13:17:a0:00 [ether] on eth0
? (199.161.101.189) at ff:63:8a:1f:83:00 [ether] on eth0
? (199.161.100.153) at b3:ab:ef:43:1d:40 [ether] on eth0

得到接收方 MAC 地址後,讀取自身網路卡 ROM 的 MAC 地址,塞入 MAC 頭,目前的資料包呈現為:

file

ICMP

ICMP 協議是 IP 的一個組成部分,必須由每個IP模組實現。

主要用於在 IP 主機、路由器之間傳遞控制訊息。控制訊息是指網路通不通、主機是否可達、路由是否可用等網路本身的訊息。

網路卡與驅動

通過協議棧生成的網路包只是記憶體中的一串二進位制資訊,還是無法直接進行傳送。需要將數字資訊轉換成電訊號,計算機底層實際上也是各種邏輯電路的組合,通過電晶體等硬體改變高低電壓。轉換成數字訊號後,就可以在網線上進行傳輸。這就是資料真正的傳送過程。同時網路卡負責的部分也被稱為 Ethernet Frame

file

網路卡負責執行這一操作,但是要控制網路卡,必須依靠網路卡驅動程式,它內建了網路卡行為的一些方法。具體步驟如下:

  • 網路卡驅動從 IP 模組獲取到網路包後,會複製其二進位制資訊至網路卡內的快取區。為了區分這一段段的資料,我們需要一套規則來傳輸二進位制,比如多少電訊號為一組,如何識別開頭和結尾等等。
  • 因此我們在二進位制資訊的起始位置新增報頭和二進位制幀的起始分解符,用來表示包的起始位置
  • 在資料包結尾加上 FCS,也被稱為幀校驗序列,檢查包在傳輸過程中是否損壞。
  • 最後網路卡將包轉換為電訊號,通過網線和光纖等物理介質進行傳輸。

最後整個資料幀呈現如下圖:

電訊號護航者

中繼器

由於電訊號在傳輸過程中會不斷衰減,為了不讓訊號衰減對通訊質量產生影響,產生了中繼器。它僅做放大訊號作用,能把訊號傳導偏遠的地方。

集線器

我們假設參與網路連結的雙方都只有一個網路介面,那麼只能夠建立一對一的通訊,從前文也可以發現,我們需要有廣播這樣一對多的場景,廣播也就是將訊號進行復制,這就是集線器的作用,並且可以將電訊號整形再放大。它工作於物理層。

順便提一下它和交換機的區別,它沒有交換機的智慧記憶和學習能力,也不具備交換機所具有的 MAC 地址表。它傳送資料時都是沒有針對性的,可以說它就是廣播傳送的代名詞。

網橋

自從有集線器以來它解決了一對多的效率問題,同時也帶來了問題,在真實的網路環境中,很有可能有多個集線器連線在一起,但由於是用來做廣播通訊,會互相沖突,因此我們需要能夠有效隔離各個子網,這就是網橋。名稱也非常形象。它位於資料鏈路層,而集線器是在物理層。因此它可以有效的控制讓廣播通訊僅僅在於一個區域性,區域性和區域性中間用網橋連線。

網橋原理

現在我們來介紹下網橋是如何解決廣播衝突的。網橋只有兩個埠,連線兩個埠的網路被切分成 A、B 兩個子網,網橋內部會為每個子網維護一張表,一開始表是空的,網橋會分別根據 A、B 子網傳送的資料包,並解開 MAC 頭部獲取源 MAC 地址,並記錄在對應的表中,並轉發給另一子網。工作一段時候後幾乎可以記錄下 A、B 子網中所有的機器的 MAC 地址。此時假設網橋接收 A 子網的資料包,它還是會拆解 MAC 頭部檢視接收端 MAC 地址。如果發現 A 表已經記錄了此 MAC 地址,說明這不需要廣播給 B 子網,A 子網內就可以解決,閘道器會丟棄此資料包,如果 A 表不存在介面端 MAC 地址,則轉發給 B 子網,再檢視源 MAC 地址,如果不存在則繼續補充在 A 表上。到此就徹底解決了因集線器整形擴大資料包後子網間廣播衝突的問題。

在實際環境中,網橋內部不一定有兩個子表,也可能是收集在一起的,具體要看內部實現決定。

交換機

網橋是切分一個區域網一分為二,也解決了廣播衝突問題,但歷史的車輪總在向前,由於網橋是資料鏈路層的廣播通訊,A 和 B 通訊的時候,C 和 D 就沒法通訊。就像一座小橋負載有限,無法讓多人一起通過。為了能夠實現多對多的通訊,於是多埠的網橋誕生了,這就是交換機。

電訊號與交換機

我們回到正軌,網路卡根據乙太網協議給二進位制資料新增起始符和 FCS,並轉換為電訊號進行傳送。

之後電訊號通過網線到達交換機網線介面,交換機內模組接收後會將電訊號轉換為數字訊號,數字訊號表示讓資訊引數在給定範圍內表現的更加連續,而不是離散。與之相對的是模擬訊號。

交換機的一個重要的作用就是確保資料包能夠原樣的轉發到目的地。他會拆解乙太網頭部獲取 FCS 校驗錯誤,如果資料沒問題則進入交換機緩衝區,之後部分基本和之前網路卡的概念相同,但是工作方式和網路卡不一樣,因為網路卡的 ROM 中有 MAC 地址,而交換機沒有。取而代之的是交換機會維護一張 MAC 地址表。地址表主要包含兩個資訊:

  • 記錄下接收方 MAC 地址的資訊
  • 記錄下此接收方的裝置連結在交換機的哪個埠上。

file

細心的同學應該發現了,這部分和網橋非常相似,只是網橋只有兩個埠,通過拆表記錄,可以不記錄埠位置資訊。如果目前的資料包和 MAC 表上記錄的 MAC 地址匹配上了,就會直接轉發到對應的埠。如果找不到指定的 MAC 地址,很可能此地址背後的裝置還沒有和交換機傳送過包,或者因為持續沒有工作,導致交換機把它從地址表中刪除了。此時只能和廣播一樣,傳送給所有的埠,前文也提過,在同一乙太網中,設計之初就是以廣播的形式傳送給整個網路的所有裝置,只有接收者才會接收包,其他裝置會忽略。接收方返回響應後,交換機會對其 MAC 地址進行記錄。

除了沒有記錄的 MAC 地址會轉發到除了源埠外的所有埠外,如果接收地址滿足廣播地址,也會觸發同樣的行為,常見的廣播地址有:

  • MAC 地址的 FF:FF:FF:FF:FF:FF
  • IP 地址的 255.255.255.255

閘道器

前文講述路由表時提過,如果沒有比配到預設閘道器的情況下,是可能不需要閘道器的,因此我們假設之前的接收方 IP 地址匹配到了預設閘道器。

預設閘道器一般就是路由器的別稱,到達路由器時也可以比作高速路的關卡,資料包準備離開子網了。下文我們以路由器代指閘道器。

路由器,也被稱為三層網路裝置,路由器每個埠都有 MAC 地址和 IP 地址。所以它可以作為乙太網的傳送和接收端,從此角度來看,它和網路卡是一樣的。我們來看看路由器的工作流程:

  • 路由器會拆解乙太網首部,驗證 FCS 校驗,如果沒有問題進入下一步
  • 接下來拆解 MAC 首部,檢視接收方的 MAC 地址是否是自己,不是自己就丟棄資料包
  • 如果是傳送給自己的包,此 MAC 首部的任務徹底完成,便完全刪掉 MAC 頭部。繼續拆解 IP 首部,讀取到 IP 地址。
  • 接著查詢自身的路由表,這和 IP 層查詢路由表的操作一致,先驗證子網掩碼,再看具體的子網 IP
  • 如果閘道器為空,則表示對應的 IP 地址就是目標地址,已經抵達終點。
  • 如果沒有匹配上路由表的子網,說明還未抵達終點,繼續把資料包轉發給路由器的預設閘道器

遞迴至對端

路由器會根據查詢到的預設閘道器 IP ,通過 ARP 獲取 MAC 地址,並且也具有 ARP 快取,查詢到 MAC 地址後,給資料包新增 MAC 頭,之後資料包加上乙太網首部,通過埠轉發給其他閘道器。雖然路由器讀取了 IP 包的目標 IP,但是傳送方和接收方的 IP 地址是永遠不會被改變的。

轉發到其他閘道器後還會遞迴這些步驟,進行閘道器到閘道器的中轉,直到抵達對端 IP。

到達接收端後會依次去除乙太網首部、MAC 首部、IP 首部、TCP 首部,最後讀取 HTTP 資訊。到此 test.com 成功接收到了 get 請求,向我們傳送資原始檔。

伺服器的 HTTP 程式看到,原來這個請求是要訪問一個頁面,於是就把這個網頁檔案封裝在 HTTP 響應報文裡。
HTTP 響應報文也需要穿上 TCP、IP、MAC 頭部,不過這次是源地址是伺服器 IP 地址,目的地址是客戶端 IP 地址。
套上各種首部後,再次從網路卡傳送出去,只交換機轉發到閘道器路由,路由器就把響應資料包發到了下一個路由器,接著遞迴過程,直到跳到了客戶端的路由器,路由器扒開 IP 頭部發現確實是給本子網的資訊,於是把包發給了子網交換機,再由交換機轉發到我們一開始的傳送端。
傳送端 OS 收到了伺服器的響應資料包後,去除各種頭部,拿到最後 HTTP 的響應報文,通過 IPC 將包發給 Network Service

Network Service 收到報文後判斷響應狀態碼,還記得我們一開始的訪問地址嗎? test.com 因此返回了 301 狀態碼,我們可以通過 curl -I test.com 檢視:

HTTP/1.1 301 Moved Permanently
Server: nginx/1.18.0
Date: Tue, 07 Sep 2021 03:21:49 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Keep-Alive: timeout=20
X-DIS-Request-ID: 241ea10b621b0644e9844c0f52ef76e1
Location: http://www.test.com/

此時 Network Service 會自動構建新的請求,請求的目標為響應報文的 location ,那麼回到本文構建請求的環節,重新走一遍整個流程,值得注意的是現代瀏覽器會預設開啟 connection: keep-alive 這會複用之前建立的 TCP 連結,加快請求速度。直到 Network Service 再次收到響應。

本次收到響應後,狀態碼正常,接著檢視響應頭部 content-type。它是 MIME 的子集。若內容無法解析,瀏覽器會啟動自動下載,如果為 text/html,就正式進入編譯篇。可以通過 curl -i [https://www.test.com/](https://www.test.com/) 檢視:

HTTP/1.1 200
Server: nginx/1.18.0
Date: Tue, 07 Sep 2021 03:39:19 GMT
Content-Type: text/html
Content-Length: 8859
Connection: keep-alive
Keep-Alive: timeout=20
ETag: "5e53086c-229b"
X-DIS-Request-ID: c821b8e4044843e8855e76558a610532
Set-Cookie: dis-request-id=c821b8e4044843e8855e76558a610532; secure
Set-Cookie: dis-timestamp=2021-09-06T20:39:19-07:00; secure
Set-Cookie: dis-remote-addr=61.175.192.50; secure
X-Frame-Options: sameorigin

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
...

最後,如果客戶端要離開了,向伺服器發起了 TCP 四次揮手,至此雙方的連線就斷開了。

筆者在到端後省略了 TCP 首次到端後進行的二次握手,QUIC 以及 TLS 的校驗等工作。它們在 HTTP 首次響應前就被完成。

網路的導航,是從輸入 url 到最終獲取到檔案的過程。其中牽扯到瀏覽器架構、作業系統、網路等一系列知識。本文將從各個角度詳細論述這一過程,涉及廣度與深度。如果您是已經有一定基礎的同學,那麼本文可以快速帶你係統化整理碎片化知識。