一文詳解 TCP/IP協議棧

前言

一切還要從那個經典問題出發:

當你在瀏覽器中輸入 https://www.google.com/ 並且按下回車之後發生了什麼?

Whaaat!!! 都 9012 年了你還在問這個問題!

解析 URL,DNS 查詢,獲得服務器 IP 地址,向目標地址發送 HTTP 請求,服務器收到,響應,返回頁面,瀏覽器接收,渲染,bingo!

你露出滿意笑容,又是一個滿分回答。

停!還沒完呢。請問這位同學,你發送的請求是怎麼到達目標服務器的,服務器的響應又是如何回來的?

你陷入沉默,說了一些 TCP 協議、OSI 模型、路由器之類的詞。

此時問題又來了:瀏覽器又是如何將請求發送出去的?

空氣中的沉默加深了幾分,彷彿要滴出水來······

這篇文章,我將以網絡通信常用的 TCP/IP 模型爲主,解釋網絡通信涉及的各個階段,不管是常規的頁面訪問,還是我們業務中常見的 HTTP 請求,都包含在這樣的過程中,瞭解整體對前端開發者們很有益處。開始吧!

目錄

  1. TCP/IP 參考模型是什麼
  2. 一次完整的網絡通信過程
  3. 各個階段的深入理解

一、TCP/IP參考模型是什麼

在進入具體的解釋之前,我們對 TCP/IP 協議的前世今身做一個簡單回顧。

所謂無規矩不成方圓,網絡誕生之初,爲了保證網絡通信的有序進行,相關組織開始着手製定各種通信協議,例如最早的網絡控制協議(NCP),到後來耳熟能詳的 OSI 七層協議等,整個因特網在這些協議的制約下迅速發展。事物總是發展變化的,技術自然更新換代。到上一世紀 80 年代,美國國防部的 ARPA 網(也就是阿帕網,互聯網的鼻祖)項目中,TCP、IP 協議最早被提出來得到應用,並且由於其優異性迅速成爲互聯網通信的主流通用協議。

這一協議最早得名是因爲兩個最重要最先被提出的協議 TCP 和 IP,後來,互聯網通信的各類協議(HTTP、IP、DNS、TCP、ARP)整體都被納入這一協議體系中,被統稱爲“TCP/IP 協議族”。

也就是說,TCP/IP 協議族最早的確只有 TCP 和 IP 兩個協議,現在則是一系列與網絡通信有關的各類協議的集合。對應這一協議族,同時發展出了TCP/IP 參考模型,這一模型是一個抽象出來的分層模型,TCP/IP 協議族中的所有協議被歸類到這一模型的 4 個層次中,每一層相互獨立,下一層爲上一層提供服務,各個層次間互相協作,完成了互聯網通信的主要工作。

這四個層分別是:網絡訪問層(又叫數據鏈路層或者網絡接口層)網絡層傳輸層應用層,大家通常將這四個層次與更爲詳細的OSI七層模型映射:

在 TCP/IP 參考模型中,各項通信協議各有歸屬,例如我們在瀏覽器中常用的:HTTP(超文本傳輸協議)、DNS(域名系統)、FTP(文件傳輸協議)、SMTP(簡單郵件傳輸協議等)都是屬於應用層協議,而 TCP、UDP 則屬於傳輸層協議,IP 則屬於網絡層協議。更多的通信協議,大家可以搜索瞭解。我們這篇文章主要分析以 HTTP 爲主的通信過程。


(常見協議)

二、一次完整的網絡通信過程

爲了對 HTTP 請求的通信過程有一個更好的理解,我將從 TCP/IP 四個層次出發,對應各個層次的通信實體,或者媒介(例如瀏覽器,路由器以及網線等),看各個協議是如何在這些通信實體中發生作用,將一個請求發送到服務器,服務器的響應又是如何趕回來的。

整體來講,一次完整的通信,很像快遞郵遞包裹,物品被加上包裝,寫上地址信息和聯繫方式,經過一個又一個的快遞中轉站,到達收貨人的位置,在網絡請求中,請求的數據就是需要快遞的物品,IP 地址和 MAC 地址就是通信的地址,網線和路由器、交換機和集線器等就是運輸道路和快遞中轉站,網絡協議則可以看做快遞員和快遞政策,而和快遞類似的是,網絡通信也會出現丟包(包裹損壞)等情況。所以說,一個簡單的 HTTP 請求,要完整地拿到請求信息,中間有若干操作系統組件、通信協議、通信實體參與,才能保證通信的順利進行。

那麼,這個快遞過程,具體是怎麼進行的呢?

其基本原則是,通過分層的順序,發送端由上往下發送,接收端則由下向上接收。爲了保證數據順利發送到下一個層次,會在發送在其頭部加上一些必要的信息,這些信息是爲了保證數據的完整和符合需求的約束信息。發送 HTTP 請求時,經常會設置 Request Header,例如 Content-Type:text/html 是向服務器說明,我需要的是 HTML 頁面,你返回其他東西是不行滴。

這些頭部信息還可能包含協議信息,請求路徑、請求方法等,但這僅僅是發生在應用層,整個通信過程,經過四個層次,會被依次加上 HTTP 請求頭,TCP 頭部、IP 頭部以及以太網頭部。數據加上這些頭部信息之後,纔會被髮往下一層,數據經過重重包裝,從你的網卡出發,穿過若干路由器,網線,交換機,到達目標服務器所在的子網,服務器要做的則是相反的過程,它會根據前面加上的頭部信息,層層解析,最後到達服務器被處理(處理就是服務端程序代碼的責任),之後再將響應數據返回。整體過程如下圖:


(圖片來自謝希仁版《計算機網絡》)

數據在各個層次間依次傳遞,其傳遞的數據單位我們可以統一理解爲數據包,數據包在不同的層次有不同的稱呼,我們熟悉的是,其從應用層出發時,將之稱爲 HTTP 請求消息(message)或者報文,爲其加上消息頭之後,發送至傳輸層,加上 TCP 頭部,叫它報文段(segment),到網絡層,加上 IP 頭部,成爲 IP 數據,到了最後的網絡訪問層,其已經套上層層包裝,在這裏在爲其加上以太網首部的同時,還會加上一些尾部信息,搖身變爲幀(framing),這個過程叫做封裝過程。不管怎麼叫,數據在各個層次間,是以數據包的形式傳遞,當數據量大時,還會出現分包傳遞的情況。

接下來我將這個過程進行擴展,看看在輸入 https://www.google.com/ 之後,TCP/IP 的各個層次間,前面提到的通信協議是如何參與其中的:

三、各階段發生了什麼

1. DNS 查詢

URL 只是爲了人類更友好識別網絡程序而設置的互聯網資源的定位標識,瀏覽器首先會對 URL 進行解析,獲取到請求的協議(https),域名(google.com),路徑(/),由於沒有其他子域名,也就是查詢 google 的默認主頁。解析完畢後,DNS 出場。

DNS,也就是域名查詢系統,負責 URL 對應的 IP 地址的查詢,每個操作系統會內置 Socket 協議庫,協議庫中有解析器(resolver)專門負責這個過程,又是一個漫長的過程(當然我們等的時間不會很長),這裏不再具體展開,感興趣的同學可以看看我在文尾列出的參考資源 How DNS works,其用生動有趣的動畫形式展示了這一過程,十分有趣。

經過 DNS 查詢,瀏覽器拿到了請求資源的 IP 地址。IP 地址,又叫網際協議地址,是分配給網絡上設備的數字標識,也就是說,只要聯網的設備,例如我們的電腦手機以及路由器等都是有 IP 地址的。其是我們的請求數據識別服務器在網絡中的位置的必須標識。

2. Mac 地址查詢

IP 地址在網絡層使用,但在實際的數據鏈路上傳遞數據時,在同一個鏈路中不同的計算機,其必須要使用另一個地址來識別—— Mac 地址,又叫做物理地址。

說到這裏,岔開一句,在網絡通信中,我們通常提到三個地址:IP 地址、Mac 地址以及端口號,三者分別代表的是:

IP地址:網絡中互聯的主機和路由器的標識。
Mac 地址:每個網卡硬件的物理地址。
端口號:識別同一個主機上不同的應用程序,也可以理解爲程序地址。

所以,在一個網絡通信中,我們需要用到五大識別符:源IP地址、目標IP地址、協議、源端口號和目標端口號。

言歸正傳,Mac 地址是每一個網卡被生產的時候就寫死在其中的,所以其是不變且唯一的,那麼如何得到服務器的 Mac 地址呢,又一個協議閃亮登場——ARP。

ARP,英文全稱叫做 Address Resolution Protocol,也就是地址解析協議,其作用是,根據IP地址獲取通信設備的物理地址,其工作原理類似於以前我們常見的廣播,它會將包發送給同一以太網的所有主機,若目標主機的 Mac 地址與對應的 IP 地址命中,則找到了目標,但是,若每次查詢都要給所有的主機發送 ARP 請求,網絡中則會出現很多 ARP 包,因此,每個主機都會有一個 ARP 緩存,裏面存放着常用的 MAC 地址,主機會優先從緩存中查詢是否有需要的信息,如果有,就不再發送廣播。Mac 地址會在網絡層加入 IP 頭部發往網絡訪問層,其實你會發現,爲了提高效率和性能,網絡通信中隨處可見緩存,HTTP 緩存、DNS 緩存以及 ARP 緩存等。值得了解的是,在IPv6中,有一個 NDP(鄰居發現協議)替代 ARP 來完成這個工作。

3. 數據傳輸——套接字來幫忙

我們前面提到操作系統中有一個協議庫,負責本機中網絡通信的很多功能,在 DNS 查詢的時候,協議棧中的解析器就參與其中,當應用層獲取到服務器的 IP 地址和 Mac 地址,已經擁有了數據傳遞的必要條件,接下來,瀏覽器會向操作系統的協議庫發出委託指令,調用其中 Socket 庫中的程序組件,建立套接字(socket),套接字的本質可以理解爲一個數據通道的出入口,其執行的是一個“打開,讀/寫,關閉”的流程。

在進行數據傳輸時,客戶端和服務器都會創建一個套接字,之後調用 socket 庫中的 connect 組件,該組件會依據描述符(套接字的匹配令符,與服務端的套接字的接頭暗號),服務器的 IP 地址和端口號,在瀏覽器和目標服務器之間建立一個傳輸通道(實際上並沒有真實的通道,中間隔着N多網關、路由器和防火牆,這樣說更好理解),而大名鼎鼎的 TCP 三次握手實際就是發生在這個階段,我們會在後面具體介紹 TCP 到底做了什麼。

通道建立之後,接下來就是數據的讀寫操作,我們的程序代碼無法直接控制套接字,它依然會委託 Socket 庫中的組件來完成數據的讀寫(write 和 read),數據傳輸完成後,服務器會主動執行斷開操作,調用 Socket 庫中的 close 組件斷開連接,瀏覽器發現之後,也會執行斷開操作,數據傳輸完成。

可以看到,在應用程序代碼的背後,操作系統中內置的組件庫和互聯網協議互爲網絡通信的左膀右臂,共同負責整個通信過程,缺一不可。

4. 三次握手與四次揮手

TCP 和 UDP 是傳輸層中的兩個主要協議,兩者的區別是:前者是面向連接的(實際就是套接字管道)、可靠的流協議,後者則並不可靠,它採取一種“盡最大努力”的傳輸策略,大家可以記住,瀏覽器、郵件等應用程序一般使用 TCP 傳遞數據,而像 DNS 查詢等較短的收發則使用 UDP。我們主要介紹 TCP。

整體來講,之所以要在發送數據之前建立套接字,正是因爲 TCP 是面向連接的傳輸協議,在傳輸協議面前,你發送的內容無關緊要,它們會將之看爲一串具有一定長度的數據。

爲了保證數據傳輸的可靠,TCP 在套接字建立連接時,採用“三次握手”策略傳輸數據。

下面是一張三次握手流程圖:

圖中的 syn 是 Synchronize Sequence Numbers 的簡寫,也就是同步序列編號,擁有數據傳輸序列的標識。

ack 指的是acknowledgement,表示確認。

通俗來講,是這樣一個過程:

第一次握手:客戶端嘗試連接服務器,向服務器發送syn包(同步序列編號),syn=j,其進入SYN_SEND 狀態等待服務器確認:我已準備好,隨時出發!

第二次握手:服務器接收客戶端 syn 包並確認(ack=j+1),同時向客戶端發送一個 syn 包(syn=k),也即 syn + ack 包,此時服務器進入SYN_RECV 狀態:收到請求,隨時接收。

第三次握手:客戶端收到服務器的 syn + ack 包,向服務器發送確認包 ack(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED 狀態,完成三次握手:OK,我知道了,那我們開始發送數據。

你可能會覺得相當麻煩,確認一次就可以了嘛,收到了嗎?收到,傳輸,爲什麼需要三次握手呢?

其實,爲了保證數據傳輸的絕對可靠,三次確認是最少的次數,試想這樣一種情況:在第一次得到確認後,客戶端發送數據出去,但是這個數據由於中途網絡等問題,遲遲到不了服務端,服務端選擇關掉通道,但通道關閉後,剛剛的數據又跑過來了,這時候,服務端會將這個數據視爲來自客戶端的新的傳輸請求,同意建立連接,而建立一個新的連接,但是,客戶端並沒有發送數據,所以,它會忽視服務端的確認,也不會迴應給服務端,服務端就在傻傻滴等待,但這種等待是沒有結果的,這就造成了服務端資源的浪費。

我在知乎上看到另外一種理解:之所以需要三次握手,本質的原因是,傳輸信道是不可靠的,你不知道網絡會在什麼時候出什麼樣的問題,所以,爲了保證傳輸的確可靠,三次握手是理論上的最小值。

TCP 傳輸數據時,並不是將所有數據一股腦全丟出去,而是會將數據存放在一個的發送緩衝區,並且會等待應用程序的下一段程序到達,之所以這樣幹,是因爲一收到數據立馬發送,很可能會造成信道的浪費與閒置,導致網絡效率太低,就像明明很寬的馬路,非要像過獨木橋一樣通過。

而 TCP 如何決定數據什麼時候會發送,其擁有一套計算機制,整體來說,是按網絡包的長度以及應用程序送包過來的時間,雖然你的數據很小,但你半天不發送,我也不會一直傻等着。

還有一種情況是,當發送過來的數據包很大時,會在緩衝區對包進行拆分發送,拆分的時候,TCP 模塊會計算好每一塊數據的開頭和結尾的字節數,並將之寫在 TCP 頭部信息中,以待對方確認,根據這些起始序號,接收方可以判斷是否有數據遺漏的情況。

每發送一個包,就需要等待 ack 包的確認,這也是一種資源浪費,所以爲了利用等待 ack 的空閒時間,數據可以不用等待 ack 直接發送,但是,這會出現一種情況,一邊一味地發送,但超過了接收方的處理能力,就會出現網絡擁堵,得不償失。爲了解決這個問題,TCP 採取了一種滑動窗口的發送策略,它的原理是這樣的:

滑動窗口實際上是數據流量控制策略,我們可以將所有的數據想象爲一個序列,而窗口則是數據可處理的數據大小,接收方會將窗口的大小寫入 ack 包的頭部信息中,告知發送方,發送方會據此動態調整發送的速率,由於傳輸的持續進行,這個窗口大小會動態變化,因此達到了流量的控制,其本質的目的是,不要發的太快,讓接收方處理不過來。


(滑動窗口示意圖)

接下來的路

上面我們只提到發送和接收,但是在瀏覽器和服務器之間,可能隔着極多的網關、路由器、交換機等,數據是如何在龐大無比的因特網上找到服務器的呢?

這就是網絡層協議的用武之地了。

實際上,這一切在請求發送之前已經準備好了,我們使用 DNS 和 ARP 等方式,獲取到了目標服務器的 IP 地址和 Mac 地址,這兩個地址引導發送的數據包到達服務器,IP 地址用於在網絡中的所有主機中匹配通信的目標主機,IP 地址包含網絡標識主機標識,前者用於區別不同的網段,後者找到同一網段的不同主機,數據到達路由器時,路由器會根據網絡標識將數據轉發到相應的網段,到達相應的網段之後,又會根據主機號,到達真正的主機。

說到這裏,大家可能會有一個疑惑,既然 IP 地址都能找到目標服務器,爲什麼還需要 Mac 地址

請容我解釋:

在同一個網段內通信時,只用知道 Mac 地址,就可以精準找到目標,但是,互聯網上的網絡數量無比龐大,網絡被各大運營商分割爲多個網段,每個網段又有多個子網,如果僅僅知道一個 Mac 地址,要在所有的網絡設備進行匹配,就是相當大的工程了,這時候,IP 地址的妙用體現出來了:它可以用來找路,這就類似於快遞中的省市地區等,而 Mac 地址可能是唯一的名字,你在全中國找一個小強的人那就太多了,但是你要具體到某一個村或小區,就很簡單了。

另外一個原因是,交換機這個東西,只認 Mac 地址,就如同路由器根據 IP 地址決定網絡請求的轉發路線,交換機通過 Mac 地址來確定具體的網卡位置。

路由器、交換機、網線、光纖等正是 TCP/IP 四層協議的最底層,網絡訪問層,也就是 OSI 模型中的數據鏈路層和物理層,在這一層,數據被加上以太網首部,封裝成幀,在各個路由器之間轉發傳送,經常來說,路由器之間的傳送又有常用的點對點協議,也就是PPP(Point-To-Point Protocol),這是一個網絡訪問層協議,PPP會在幀的前後加上幀界定符進行發送。最後,到達服務器所在子網,會將消息轉交網絡層向上傳遞。

向上傳遞的過程如前所述,乃是解析頭部信息,最終到達服務器的相反過程,不再細述。

這個過程我們可以在前面的整體過程圖清晰看到,請求數據到達服務器所在子網的路由器之後,又是一個由下而上的過程,這與客戶端發送數據基本相反,每經過一個層次,都會解析前面添加到請求數據上的多層頭部信息,交由上一層處理,在整個 TCP/IP 協議棧的幫助下,我們的請求數據終於到達了目標服務器,穿過服務器的網卡,服務器操作系統同樣有協議棧來負責找到服務端程序對應的端口,將數據交由服務端應用程序處理。

服務器會根據我們的請求頭信息,處理返回結果,之後,請求響應信息重新出發,再次回到瀏覽器。

瀏覽器收到請求後,經過解析、渲染過程,頁面呈現在我們面前。

參考鏈接

  1. How DNS works
  2. 謝希仁《計算機網絡》
  3. 戶根勤《網絡是怎樣連接的》
  4. what-happens-when...
  5. 通俗大白話來理解TCP協議的三次握手和四次分手
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章