一文串聯 HTTP、TCP、IP、乙太網

孟思行發表於2020-12-28

最近部門組織了一次前端效能優化交流會,大家從輸入頁面 URL 到最終頁面展示內容這個過程提出了許多優化點。但同時發現很多同學對 HTTP 協議層的知識不能串聯起來,於是整理了這篇文章,希望可以給大家帶來一絲靈感。

當我們在頁面上發起一個 AJAX 請求的時候,在網路協議層面都經歷了哪些內容?

// 發起請求
fetch('https://baidu.com')
// 協議層1...
// 協議層2...
// 協議層3...
.then(res=>
  // 得到結果
  console.log(res)
})

如上述程式碼所示,我們對 baidu.com 發起了一個網路請求,最終在 then 方法中得到了具體的響應內容。

使用 Wireshark 抓包結果如下:

圖中可以看到,請求 baidu.com 時,首先通過 TCP 3 次握手建立連線,然後通過 HTTP 傳輸內容,最後通過 TCP 4 次揮手斷開連線。

真實的過程更加複雜,我們主要分析以下幾點:

  • 建立連線階段

    • DNS 域名解析(應用層)
    • 建立 TCP 連線(傳輸層)

      • 通過 IP 定址找到目標伺服器(網路層)
      • 通過 Mac 定址找到伺服器硬體介面(資料鏈路層)
      • 通過網線向伺服器硬體介面傳輸位元資訊(物理層)
  • 傳送資料階段

    • 建立 SSL 安全連線(傳輸層)
    • 傳送 HTTP 請求(應用層)

建立連線階段

要獲取 baidu.com 的網頁內容,就需要和 baidu 伺服器建立連線,怎樣建立這個連線呢?

  1. 通過 DNS 獲取 baidu 的 IP 地址。
  2. 建立 TCP 連線。

DNS 域名解析

通過 DNS 解析,我們就能找到 baidu 伺服器對應的 IP 地址。

如圖:

經過 DNS 解析後,我們就能得到 baidu.com 的 IP 地址了:39.156.69.79 和 220.181.38.148,通常客戶端會隨機選中一個 IP 地址進行通訊。

域名的解析步驟

其實 IP 不一定要通過 DNS 解析才能獲取,它通常會被客戶端快取,只有在 DNS 快取都沒有命中的時候才會請求 DNS 伺服器。

判斷步驟如下:

  1. 判斷瀏覽器是否有快取 IP 地址。
  2. 判斷本機是否有快取該 IP 地址,如:檢查 Host 檔案。
  3. 判斷本地域名解析伺服器是否有快取 IP 地址,如:電信,聯通等運營商。
  4. 向 DNS 根域名解析伺服器,解析域名 IP 地址。
  5. 向 DNS 二根域名解析伺服器,解析域名 IP 地址。
  6. 以此類推,最終獲得 IP 地址。

建立 TCP 連線

有了 IP 地址之後,客戶端和伺服器端就能建立連線了,首先是建立 TCP 連線。

TCP 是一種面向連線的、可靠的、基於位元組流的傳輸層通訊協議。

在這一層,我們傳輸的資料會按照一個個的位元組裝入報文中,當報文的長度達到最大分段(MSS)時,就會傳送這個報文。如果傳輸的報文很長,可能會被拆分成多個 TCP 報文進行傳輸。

TCP 報文頭如下:

我們主要看以下幾點:

  • 源埠、目的埠。
  • 序列號:seq,報文的唯一標識。
  • 確認號:ack,報文的確認標識,便於確認 seq 是否已經收到。
  • TCP 標記:

    • SYN 為 1 表示這是連線請求或是連線接受請求。用於建立連線和同步序列號。
    • ACK 為 1 表示確認號欄位有效。注意這裡大寫的 ACK 只是一個標記,和確認號 ack 並不相同。
    • FIN 為 1 表示要求釋放連線。
  • 視窗:表示傳送方可以接收的位元組數,即接收視窗大小,用於流量控制。

接下來,我們看一下 TCP 是怎樣建立連線的?

如圖所示,建立 TCP 連線需要 3 個步驟,俗稱三次握手。

  • 第一次握手:客戶端向伺服器端傳送序列號 seq=x 的標識,表示開始建立連線。
  • 第二次握手:伺服器端回發一個 ack=x+1 的標識,表示確認收到第一次握手,同時傳送自己的標識 seq=y。

    • 客戶端確認自己發出的資料能夠被伺服器端收到。
  • 第三次握手:客戶端傳送 ack=y+1 的標識,標識確認收到第二次握手。

    • 伺服器端確認自己發出的資料能夠被客戶端收到。

經過了 3 次握手,即保證了客戶端和伺服器端都能正常傳送和接收資料,TCP 連線也就建立成功了。

TCP 可靠傳輸原理

上文中說到,TCP 是可靠的傳輸,這是為什麼呢?

這是因為 TCP 內部使用了 停止等待協議 ARQ ,它通過 確認重傳 機制,實現了資訊的可靠傳輸。

例如:

  • 客戶端傳送資料 M1
  • 伺服器端確認資料 M1 收到
  • 客戶端傳送資料 M2
  • 伺服器端確認資料 M2 收到
  • 依次類推 ...

在這期間,如果某一條資料很久都沒有得到確認,客戶端就會重傳這條資料。這樣一來,對於與每一次傳送的資料,伺服器端都得到了確認,即保證了資料的可靠性。

雖然 ARQ 可以滿足資料可靠性,但每次只能傳送和確認一個請求,效率太低了,於是就產生了連續 ARQ 協議。

連續 ARQ 協議 會連續傳送一組資料,然後再批量等待這一組資料的確認資訊,好比把單執行緒 ARQ 變成了多執行緒,大大提高了資源的利用效率。

如:

  • 客戶端傳送資料 M1、M2、M3、M4。
  • 伺服器端確認資料 M4 收到,表示 M4 及之前的資料都收到了。
  • 客戶端傳送資料 M5、M6、M7、M8。
  • 伺服器端確認資料 M8 收到,表示 M8 及之前的資料都收到了。

在這個流程中,伺服器端不需要對每一個資料都返回確認資訊,而是接收到多個資料時一併確認,這個方式叫做 累計確認


這裡有個疑問,TCP 的每一次握手,是怎麼找到目的伺服器呢?

答:通過 IP 協議。

根據 IP 協議找到目標伺服器

IP 協議的目的是實現網路層的資料轉發,它通過路由器不斷跳轉,最終把資料成功送達目的地。

上文中的每一次 TCP 握手以及資料互動,都是通過 IP 協議去傳輸的。

IP 報文頭如下:

我們關注以下兩點就可以了:

  • 源 IP 地址
  • 目的 IP 地址

發起一個 IP 請求執行流程如下:

  1. 構建 IP 請求頭(源 IP、目標 IP)。
  2. IP 協議通過演算法,計算出一條通往伺服器端的路徑。
  3. 傳送端查詢路由表,找出下一跳的 IP 地址(通常是路由器),併傳送資料。
  4. 路由器查詢路由表,找出下一跳的 IP 地址,併傳送資料。
  5. 不斷重複步驟 4,直到找到目的區域網。
  6. 傳送資料。
路由表存在於計算機或路由器中,由目的 IP 地址、子網掩碼、下一跳地址、傳送介面四部分組成。通過目的 IP 地址,即可找到下一跳的地址,進行轉發。

例如:A 要向 G 傳送 IP 資料。

具體流程如下:

  • A 生成 IP 頭部(源 IP:A ,目的 IP:G)

    • A 查詢路由表,發現下一跳為 B,於是把資料傳給 B。
  • B 生成 IP 頭部(源 IP:A ,目的 IP:G)

    • B 查詢路由表,發現下一跳為 E,於是把資料傳給 E。
  • E 生成 IP 頭部(源 IP:A ,目的 IP:G)

    • E 查詢路由表,發現下一跳為 G,於是把資料傳給 G。
  • 到達目的地 G。

你是否有疑惑,為什麼 IP 會按照這條路徑向 G 傳輸資料呢?

其實,上圖中的路徑並非只有一條,我們通過 ABEG 到達了目的地 G,同樣也可以通過 ABCFHG 到達 G,這兩種路徑都能完成任務,為什麼 IP 不選擇 ABCFHG 這條路徑呢?

這就涉及到了 IP 定址的演算法。

IP 定址演算法

我們可以把網路中的所有計算機都看做是一個點,計算機之間的連線看做是一條線,這些點和線就組合成了一個圖。

例如:

通過上圖,我們就把複雜的網路轉化成了數學問題。IP 定址演算法,其實就是圖論中的最短路徑的演算法。

最短路徑演算法在 IP 協議中有 2 種實現:

  • RIP 協議

    • 使用距離向量演算法,確保 IP 路由跳轉的次數最小
    • 原理

      • 每個節點中都儲存有其他節點的位置資訊(跳數和下一跳的 IP)。
      • 通過和鄰居節點進行資料交換,更新自己到目的地的最短距離,不斷重複,即可得到起點到終點的最短路徑。
      • 實現簡單,開銷很小,適用於小型網路。
  • OSPF 協議

    • 使用迪傑斯特拉演算法,確保 IP 路由跳轉的速度最快
    • 原理

      • 從起始點開始,採用貪心演算法的策略,每次遍歷到始點距離最近且未訪問過的頂點的鄰接節點,直到擴充套件到終點為止。
      • 適用於大型網路。

通過以上兩個協議,我們就能找到通往目的地的路徑了。


這裡丟擲一個問題:IP 資料是怎樣從一個路由器跳到另一個路由器呢?

答:通過乙太網協議。

通過 Mac 定址找到伺服器硬體介面

IP 協議主要是用來尋找最優路徑的,具體的傳輸是由乙太網協議來做的。

乙太網屬於資料鏈路層,它主要負責相鄰裝置的通訊。原理是通過查詢交換機 Mac 表,找到通訊雙方的物理介面,進而開始通訊。

乙太網報文頭如下:

我們只用關心以下 3 個點:

  • 源 Mac 地址
  • 目的 Mac 地址
  • 校驗碼 CRC:校驗當前幀是否有效。

可以看到,乙太網層都是通過 Mac 地址進行通訊的,這裡的 Mac 地址是哪裡來的呢?

答:通過 ARP 協議。

ARP 協議 是一個通過解析 IP 地址來找尋 Mac 地址的協議。IP 地址轉換成 Mac 地址後,就能進行乙太網資料傳輸了。

例如:

當機器 A 向機器 C 傳送資料時:

  • A 構建乙太網報文(源地址:A,目的地址:C),並通過網路卡發出資料幀。
  • 資料幀到達交換機 B,交換機取出目的地址 C 的 Mac 地址。
  • B 查詢 Mac 表,根據目的地 Mac 地址,匹配 C 的硬體介面。

    • 如果找到 C 的硬體介面,傳送資料。
    • 如果未找到 C 的硬體介面,向 B 直連的所有機器傳送廣播資訊找 C,找到後會把 C 記錄到 Mac 表中。

經過上述的流程,我們就找到了目的機器的硬體介面。


通過乙太網協議,我們找到了目標機器的硬體介面,接下來要怎麼傳送資訊呢?

答:通過物理層。

通過網線向伺服器硬體介面傳輸位元資訊

在沒有 WiFi 的年代,我們只能通過插網線來進行上網,網線其實就是物理層的裝置之一。

網線可以由多種材料組成,最常見的就是光纖和電纜。

光纖和電纜的傳輸原理類似,都是通過兩個訊號來模擬二進位制資料的,一個訊號即為一個位元。

  • 電纜中:高電位表示 1 ,低點位表示 0。
  • 光纖中:光亮表示 1,光熄滅表示 0。

如:在光纖中,我們通過觀察光的閃動,即可得知傳輸的二進位制資料。

有了這些物理裝置,我們就能把複雜的資料轉換成光訊號或者電訊號進行傳輸了。

傳送資料階段

傳送資料可以分為兩個步驟:

  • 建立安全層 SSL
  • 傳送 HTTP 請求

建立安全層 SSL

本文的案例是傳送一個 HTTPS 的請求,所以在傳送資料之前,會建立一個 SSL 安全層,用於資料加密。

通常的加密方法有兩種:

  • 非對稱加密

    • A 有鑰匙,B 沒有鑰匙,且他們都有一個公共的鎖,B 給 A 傳送資料時,都會先把資料鎖起來再傳送。
    • 接收資料時,A 用鑰匙解開鎖,即可得到資料。除 A 以外,其他人沒有鑰匙,也就獲取不到資料。
    • 實現了單向通訊加密。
  • 對稱加密

    • A、B 雙方都有一把相同的鑰匙和一個公共的鎖,每次傳送資料時,都把資料放在鎖裡進行傳送。
    • 接收資料時,A、B 雙方就用各自的鑰匙來解鎖。其他人沒有鑰匙,也就獲取不到資料。
    • 實現了雙向通訊加密。

網際網路通訊是雙向的,所以我們需要使用對稱加密,可是,怎樣才能保證通訊雙方都有一把相同的鑰匙呢?

目前的解決方案:

  • 先使用非對稱加密,進行祕鑰協商,讓通訊雙方拿到相同的鑰匙。
  • 然後使用對稱加密,進行加密傳輸。

祕鑰協商過程如圖:

圖中劃重點:

  1. 客戶端傳送自身支援的加密演算法。
  2. 伺服器端選擇一種加密演算法,同時返回數字證照。
  3. 客戶端確認證照有效。
  4. 客戶端生成隨機數,並使用證照中的伺服器公鑰加密,然後傳送給伺服器。
  5. 伺服器端使用私鑰解密,獲得隨機數。
  6. 雙方使用第 2 步確定的加密演算法,把隨機數進行加密,即可獲得相同的對稱加密祕鑰。

Ok,祕鑰協商之後,我們的 SSL 安全層也就建好了。

祕鑰協商時存在一個問題:

祕鑰協商時,怎麼保證是和真正的伺服器在協商,而不是一箇中間人呢?

答:數字證照

數字證照重點關注 2 個部分:

  • 伺服器公鑰
  • 數字簽名

其中,數字簽名又是由伺服器公鑰和證照私鑰加密生成的,目的是為了防止伺服器公鑰被篡改。

有了數字證照,客戶端就能通過驗證證照,來判斷伺服器是否是真正的伺服器了。

驗證邏輯如下:

可以看到,數字證照通過同樣的演算法進行解密,如果得到相同的資訊摘要,就能保證資料是有效的,如果不一致,則會驗證失敗,拒絕後續的請求。

到這裡為止,所有的準備工作都就緒了,接下來才是傳送 HTTP 請求。

傳送 HTTP 請求

HTTP 協議其實就是制定了一個通訊規則,規定了客戶端和伺服器之間的通訊格式。

以請求 baidu 首頁為例:

如上圖所示,發起 HTTP 請求時,必須遵守以下規則:

  • 請求方法(必填) GET
  • 請求地址(必填) /
  • HTTP 協議版本(必填) 1.1
  • 其他 HTTP 頭部欄位(可選) HostUser-AgentAccept
  • 請求引數,放在空行後面(可選)

伺服器響應請求時,同樣遵守了 HTTP 響應規則:

  • HTTP 協議版本(必填) 1.1
  • 響應狀態碼(必填) 200
  • 狀態碼描述(必填) OK
  • 其他 HTTP 頭部欄位(可選) DateServerETagLast-Modified
  • 請求引數,放在空行後面(可選)

只要我們遵守這個規則,就能進行 HTTP 通訊了。

到目前為止,我們已經分析完成了資料請求的所有過程,你是否都理解了呢?

思考與總結

本文通過一個網路請求,對整個 HTTP、TCP、IP、乙太網等協議進行了流程化分析,最後再梳理一下:

  1. 請求 baidu.com。
  2. DNS 解析 baidu.com,得到 IP 地址。
  3. 建立 TCP 連線。
  4. IP 協議通過演算法,計算出一條通往伺服器最優路徑。
  5. IP 沿著路徑跳轉時,會通過 ARP 協議把 IP 地址轉換成 Mac 地址。
  6. 乙太網通過 Mac 地址,找到通訊雙方的硬體介面。
  7. 物理層通過網線作為載體,在兩個硬體介面之間傳輸位元訊號。
  8. TCP 連線建立完畢。
  9. 建立 SSL 安全層。
  10. 傳送 HTTP 請求。

最後,如果你對此有任何想法,歡迎留言評論!

相關文章