透過 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 到最終獲取到文件的過程。其中牽扯到瀏覽器架構、操作系統、網絡等一系列知識。本文將從各個角度詳細論述這一過程,涉及廣度與深度。如果您是已經有一定基礎的同學,那么本文可以快速帶你系統化整理碎片化知識。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM