最近部門組織了一次前端效能優化交流會,大家從輸入頁面 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 伺服器建立連線,怎樣建立這個連線呢?
- 通過 DNS 獲取 baidu 的 IP 地址。
- 建立 TCP 連線。
DNS 域名解析
通過 DNS 解析,我們就能找到 baidu 伺服器對應的 IP 地址。
如圖:
經過 DNS 解析後,我們就能得到 baidu.com 的 IP 地址了:39.156.69.79 和 220.181.38.148,通常客戶端會隨機選中一個 IP 地址進行通訊。
域名的解析步驟
其實 IP 不一定要通過 DNS 解析才能獲取,它通常會被客戶端快取,只有在 DNS 快取都沒有命中的時候才會請求 DNS 伺服器。
判斷步驟如下:
- 判斷瀏覽器是否有快取 IP 地址。
- 判斷本機是否有快取該 IP 地址,如:檢查 Host 檔案。
- 判斷本地域名解析伺服器是否有快取 IP 地址,如:電信,聯通等運營商。
- 向 DNS 根域名解析伺服器,解析域名 IP 地址。
- 向 DNS 二根域名解析伺服器,解析域名 IP 地址。
- 以此類推,最終獲得 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 請求執行流程如下:
- 構建 IP 請求頭(源 IP、目標 IP)。
- IP 協議通過演算法,計算出一條通往伺服器端的路徑。
- 傳送端查詢路由表,找出下一跳的 IP 地址(通常是路由器),併傳送資料。
- 路由器查詢路由表,找出下一跳的 IP 地址,併傳送資料。
- 不斷重複步驟 4,直到找到目的區域網。
- 傳送資料。
路由表存在於計算機或路由器中,由目的 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 雙方就用各自的鑰匙來解鎖。其他人沒有鑰匙,也就獲取不到資料。
- 實現了雙向通訊加密。
網際網路通訊是雙向的,所以我們需要使用對稱加密,可是,怎樣才能保證通訊雙方都有一把相同的鑰匙呢?
目前的解決方案:
- 先使用非對稱加密,進行祕鑰協商,讓通訊雙方拿到相同的鑰匙。
- 然後使用對稱加密,進行加密傳輸。
祕鑰協商過程如圖:
圖中劃重點:
- 客戶端傳送自身支援的加密演算法。
- 伺服器端選擇一種加密演算法,同時返回數字證照。
- 客戶端確認證照有效。
- 客戶端生成隨機數,並使用證照中的伺服器公鑰加密,然後傳送給伺服器。
- 伺服器端使用私鑰解密,獲得隨機數。
- 雙方使用第 2 步確定的加密演算法,把隨機數進行加密,即可獲得相同的對稱加密祕鑰。
Ok,祕鑰協商之後,我們的 SSL 安全層也就建好了。
祕鑰協商時存在一個問題:
祕鑰協商時,怎麼保證是和真正的伺服器在協商,而不是一箇中間人呢?答:數字證照。
數字證照重點關注 2 個部分:
- 伺服器公鑰
- 數字簽名
其中,數字簽名又是由伺服器公鑰和證照私鑰加密生成的,目的是為了防止伺服器公鑰被篡改。
有了數字證照,客戶端就能通過驗證證照,來判斷伺服器是否是真正的伺服器了。
驗證邏輯如下:
可以看到,數字證照通過同樣的演算法進行解密,如果得到相同的資訊摘要,就能保證資料是有效的,如果不一致,則會驗證失敗,拒絕後續的請求。
到這裡為止,所有的準備工作都就緒了,接下來才是傳送 HTTP 請求。
傳送 HTTP 請求
HTTP 協議其實就是制定了一個通訊規則,規定了客戶端和伺服器之間的通訊格式。
以請求 baidu 首頁為例:
如上圖所示,發起 HTTP 請求時,必須遵守以下規則:
- 請求方法(必填)
GET
- 請求地址(必填)
/
- HTTP 協議版本(必填)
1.1
- 其他 HTTP 頭部欄位(可選)
Host
、User-Agent
、Accept
- 請求引數,放在空行後面(可選)
伺服器響應請求時,同樣遵守了 HTTP 響應規則:
- HTTP 協議版本(必填)
1.1
- 響應狀態碼(必填)
200
- 狀態碼描述(必填)
OK
- 其他 HTTP 頭部欄位(可選)
Date
、Server
、ETag
、Last-Modified
等 - 請求引數,放在空行後面(可選)
只要我們遵守這個規則,就能進行 HTTP 通訊了。
到目前為止,我們已經分析完成了資料請求的所有過程,你是否都理解了呢?
思考與總結
本文通過一個網路請求,對整個 HTTP、TCP、IP、乙太網等協議進行了流程化分析,最後再梳理一下:
- 請求 baidu.com。
- DNS 解析 baidu.com,得到 IP 地址。
- 建立 TCP 連線。
- IP 協議通過演算法,計算出一條通往伺服器最優路徑。
- IP 沿著路徑跳轉時,會通過 ARP 協議把 IP 地址轉換成 Mac 地址。
- 乙太網通過 Mac 地址,找到通訊雙方的硬體介面。
- 物理層通過網線作為載體,在兩個硬體介面之間傳輸位元訊號。
- TCP 連線建立完畢。
- 建立 SSL 安全層。
- 傳送 HTTP 請求。
最後,如果你對此有任何想法,歡迎留言評論!