請求數據包從發送到接收,都經歷什麼?

我的 個人網站 上線了,上面可以更好的檢索歷史文章,並且可以對文章進行留言,歡迎大家訪問

之前講了「從輸入 URL 再到瀏覽器成功看到界面」中的域名是如何變成 IP 地址的,瞭解了 DNS 相關的東西。這篇文章就聊聊發生在 DNS 解析之後的操作——建立連接。也就是我們常說的三次握手

看到三次握手你可能會說,這不是面試都被問爛了的題嗎?

三次握手不就是:

  1. 服務器開始爲 CLOSE 狀態,然後監聽某個端口,此時服務器會進入 LISTEN 狀態
  2. 客戶端最初也是 CLOSE 狀態,客戶端會向服務器發送一個帶 SYN 標誌位的數據包,主動發起連接。此時客戶端會變成 SYN-SENT 狀態
  3. 服務器接收到客戶端的數據包之後,通過標誌位判斷出了客戶端想要建立連接。然後返回一個 SYNACK ,此時服務器的狀態變爲了 SYN-RCVD
  4. 客戶端收到了服務器的 ACK 之後,會回一個 ACK 給服務器,回完這個 ACK 之後,服務器的狀態就變爲了 ESTABLISH
  5. 服務器收到了客戶端回覆的 ACK 之後,服務器的狀態也變成了 ESTABLISH

這不就完了嗎?還有什麼好聊的?

這篇文章不會涉及到上面提到的什麼各種狀態的變化,包內的標誌位是什麼,而是會更加關注於底層的東西,也就是上面那些發來發去的數據包是如何發送出去的

其實不僅僅是建立連接時的三次握手,像瀏覽器中調用的很多 HTTP 接口,都會和服務器進行通信。

那這些個請求到底都是怎麼發送給服務器的呢?

這還用問?不就是發個 HTTP 請求就過去了嗎?

當然,這個答案可能是很多不瞭解網絡的人可能會說出的答案。

其實更具體、更準確的說法是通過協議棧網卡發送出去的。

其中,協議棧負責對數據進行打包,打包完成之後就由網卡將數據轉換成電信號,通過光纖發送出去了。

網卡自不必說,用來和其他的計算機進行通訊的硬件,我們常說的 MAC(Medium Access Control) 地址,其實就是網卡的編號,從其被生產出來的那一刻就被確定的一個唯一編號。MAC 地址長爲 48 個比特,也就是 6 個字節,用十六進制進行表示。

當我們知道了和我們通信的 IP 地址之後,就可以委託操作系統中的協議棧將來來自應用程序的數據,打包成數據包然後發送出去。那協議棧,具體是啥呢?協議棧其實是一系列網絡協議的總和,例如:

  • TCP
  • UDP
  • IP

不同的應用程序在進行數據傳輸的時候,可能會選擇不同的協議。例如我們使用的瀏覽器就是使用的 TCP 協議,而像之前講過的 DNS 解析就用的 UDP 協議。

那數據在協議棧中到底經歷了什麼?才變成了一個一個的數據包?

就拿我們向服務器發送一個 HTTP 請求作爲例子,我們知道 HTTP 請求中有:

  • 請求行
  • 請求頭
  • 請求體

HTTP 是屬於應用層的協議,而應用層還有很多其他的協議,每個協議所涉及到的數據也都不同,協議棧要怎麼去兼容不同協議之間的數據呢?

答案是不做兼容。對於協議棧來說,所有的數據都只不過是一堆二進制序列

那協議棧收到了這一堆二進制序列之後是不是就直接交給網卡發送了呢?

我都這麼問了,那顯然不是了...

其實協議棧在收到數據之後並不會馬上就會就發送出去,而是會先寫入位於內存的 Buffer 中。那爲啥不直接發出呢?

其實很簡單,假設你現在正在公交車的起始站,你覺得公交車會來一個人就立馬發車嗎?

顯然不是,它會等一段時間,有更多的乘客上車之後再發車。但是它又不能等太長的時間,不然後續站臺的乘客就會等的很久。

協議棧之所以不立即發出去,其實也是同樣的道理。其實這背後無非基礎兩種考慮:

  1. 數據的長度
  2. 等待的時間

應用層的程序發送過來的數據可能長度都不太一樣,有的可能一個字節一個字節的發, 有的可能一次性就傳入所有的數據。

如果收到數據就發送出去,會導致在網絡中傳輸着很多小包,而這會降低網絡傳輸的效率。

所以,協議棧在收到數據之後會等待一段時間,等數據達到一定量之後,再執行發送操作。

但是,協議棧又不能等的太久是吧?等太久了你讓正在電腦面前操作的用戶情何以堪,這種發送延遲會讓用戶體驗刷刷的往下掉。

但是吧,想做到對這兩者的平衡卻不是一件簡單的事。數據包太短,降低網絡傳輸效率,等待太長時間,又會造成發送延遲。所以協議棧索性就把控制權交給了應用程序。

應用程序可以自己控制到底採取哪種措施,例如我們常用的瀏覽器,因爲和用戶實時的在進行交互,用戶對整個頁面的響應速度也相當敏感,所以一般都會採用直接發送數據的方式,即使其數據並沒有達到「一定的量」

這一個「一定的量」到底是啥?

的確,上面都只說一定的量、一定的量,那這個量到底是多少?

要了解這個我們需要知道兩個參數,分別是:

  1. MTU(Maximum Transmission Unit)最大傳輸單元
  2. MSS(Maximum Segment Size)最大分段大小

MTU 其實就代表了上面途中數據包的最大長度,一般來說是 1500 字節。而我們需要知道數據包是由以下部分組成的:

  1. 各種頭部信息
  2. 真實數據

而從 MTU 中減去各種頭部數據的大小,剩下的就是 MSS 了,也就是實際的數據。

知道了數據包的組成和 MTU、MSS 的概念之後,我們就可以繼續接下來的步驟了。某次發送的數據,沒有超過 MSS 還好,就可以直接發送出去了。

那如果超過了 MSS 咋辦?例如我發這篇文章時所發請求的數據長度就可能超過 MSS 。

過長數據包拆分

此時就需要對數據進行拆分,按照 MSS 的長度爲單位進行拆分,將拆出來的數據分別裝進不同的數據包中。拆分好之後,就可以發送給目標服務器了。

TCP 會確保通信的服務器能夠收到數據包。傳輸時對每個字節都進行了編號,舉個例子,假設此次傳輸的數據是 1 - 1000 字節,然後服務器回的 ACK 就會是 1001,這就代表沒有丟包

這些發送過的包都會暫存在 Buffer 中,如果傳輸的過程中出錯,則可以進行重發的補償措施。這也是爲什麼在數據鏈路層(例如網卡、路由器、集線器)等等都沒有補償機制,它們一旦檢測到錯誤會直接將包丟棄。然後由傳輸層重發就好。

那要是網絡很擁堵,服務器一直沒有返回怎麼辦?

在服務器端,我們去和其他第三發進行交互時,是不是都會設定一個超時的時間?如果不設置超時時間那難道一直在這等下去嗎?

TCP 也同理。客戶端在等待服務器響應時,會有一個時間叫 ACK 等待時間,其實也是超時時間。

當網絡發生擁堵時,其實你完全也可以把網絡擁堵理解成路上堵車。此時,ACK 的返回就會變慢。如果返回時間長到了讓客戶端認爲服務器沒有收到,就有可能會重發。

並且有可能剛剛重發完,ACK 就到了。雖然服務器端可以通過序號來對包進行判重,不會造成錯誤,但是這種沒有意義的重複包,在本身網絡負擔已經很重的情況下,你還往裏懟重複的無用的數據包,這不是扯淡嗎?這明顯不行的。

那怎麼避免上面的這個情況呢?答案很簡單,稍微延長一點 ACK等待時間,這樣一來就能一定程度上避免上述的問題。但是用屁股想想應該也知道,這個時間肯定不是越長越好,再長用戶那又該等爆炸了。

除了網絡波動會影響到 ACK 的返回時間,通信的物理距離也是一個影響的因素。說白了就是這玩意兒不可能設置一個固定的時間。所以,實際上,這個等待時間動態調整的,這次稍微返回慢了點,那我下次就稍微延長一點等待時間。返回 ACK 的速度如果很給力,那麼就會相應的減少 等待

上面的概念也有一個大家很熟悉的名字,叫——超時重傳

我們來設想一個更加極端的情況,假設你們通信的網線被挖斷了,甚至機房起火了,這個時候無論你重發多少次都沒用。那 TCP 不就一直無限循環的把請求發下去了?

當然 TCP 設計時也考慮到了這種情況,其在重傳幾次無效之後,就會強制中斷通信,並拋出錯誤給應用程序。

問題又來了,客戶端在向服務器發送數據包之後,等待 ACK 的過程中,真的就只是等 ACK,其他的什麼也不做嗎?

當然不是,這樣極其的浪費資源,降低通信效率。發送完一個數據包之後,不用等待 ACK 的返回,會直接繼續發送下一個包,這就是滑動窗口

但是這樣會有一個問題,應用程序發送包發送的過於頻繁,導致服務器接收不過來了。

因爲剛剛說過,應用程序發送的時候,會將發送過的數據存儲在 buffer 中。而對於接收方也是一樣的,接收方收到消息之後,會將數據存儲在 Buffer 中,然後在 Buffer 中對收到的數據進行重組,還原成最初的應用程序發送的數據。

但是如果發送的數據太快,超過了重組的速度,緩衝區就會被填滿。而緩衝區一旦被填滿,後續的數據就無法再接收了,然後丟包就出現了。

那 TCP 是如何解決這個問題的呢?答案是 流量控制。爲了防止傳輸方發送的過快直接造成丟包,繼而觸發上面的超時重傳機制,根據接收方的接受能力,來決定發送方的傳輸速度,這個機制就是流量控制

該機制作用於接受方。在TCP報文頭部中會用一個16位的字段來表示窗口大小非常重要的調優參數。這個數字越大,則說明接收方的緩衝區越大,能夠接收更多的數據。接收方會在確認應答的時候,將自己的剩餘窗口大小寫入,隨ACK一起發送給發送方。

TCP流量控制

如果發送方接收到的大小爲0,那麼此時就會停止發送數據。這樣會有一個問題,如果下一個應答(也就是窗口大小不爲0)在過程中丟了,那麼發送方就會進入死鎖,相互等待。所以發送方會定期的向接收方發送窗口探測的數據段

好了,關於數據包的發送就介紹到這裏。之後有機會再聊聊 TCP 的擁塞控制相關的東西。

本篇文章已放到我的 Github github.com/sh-blog 中,歡迎 Star。微信搜索關注【SH的全棧筆記】,回覆【隊列】獲取MQ學習資料,包含基礎概念解析和RocketMQ詳細的源碼解析,持續更新中。

如果你覺得這篇文章對你有幫助,還麻煩點個贊關個注分個享留個言

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章