之前講了「從輸入 URL 再到瀏覽器成功看到介面」中的域名是如何變成 IP 地址的,瞭解了 DNS 相關的東西。這篇文章就聊聊發生在 DNS 解析之後的操作——建立連線。也就是我們常說的三次握手。
看到三次握手你可能會說,這不是面試都被問爛了的題嗎?
三次握手不就是:
伺服器開始為 CLOSE 狀態,然後監聽某個埠,此時伺服器會進入 LISTEN 狀態客戶端最初也是 CLOSE 狀態,客戶端會向伺服器傳送一個帶 SYN 標誌位的資料包,主動發起連線。此時客戶端會變成 SYN-SENT 狀態伺服器接收到客戶端的資料包之後,透過標誌位判斷出了客戶端想要建立連線。然後返回一個 SYN 和 ACK ,此時伺服器的狀態變為了 SYN-RCVD客戶端收到了伺服器的 ACK 之後,會回一個 ACK 給伺服器,回完這個 ACK 之後,伺服器的狀態就變為了 ESTABLISH伺服器收到了客戶端回覆的 ACK 之後,伺服器的狀態也變成了 ESTABLISH這不就完了嗎?還有什麼好聊的?
這篇文章不會涉及到上面提到的什麼各種狀態的變化,包內的標誌位是什麼,而是會更加關注於底層的東西,也就是上面那些發來發去的資料包是如何傳送出去的。
其實不僅僅是建立連線時的三次握手,像瀏覽器中呼叫的很多 HTTP 介面,都會和伺服器進行通訊。
那這些個請求到底都是怎麼傳送給伺服器的呢?
這還用問?不就是發個 HTTP 請求就過去了嗎?
當然,這個答案可能是很多不瞭解網路的人可能會說出的答案。
其實更具體、更準確的說法是透過協議棧和網絡卡傳送出去的。
其中,協議棧負責對資料進行打包,打包完成之後就由網絡卡將資料轉換成電訊號,透過光纖傳送出去了。
網絡卡自不必說,用來和其他的計算機進行通訊的硬體,我們常說的 MAC(Medium Access Control) 地址,其實就是網絡卡的編號,從其被生產出來的那一刻就被確定的一個唯一編號。MAC 地址長為 48 個位元,也就是 6 個位元組,用十六進位制進行表示。
當我們知道了和我們通訊的 IP 地址之後,就可以委託作業系統中的協議棧將來來自應用程式的資料,打包成資料包然後傳送出去。那協議棧,具體是啥呢?協議棧其實是一系列網路協議的總和,例如:
TCPUDPIP不同的應用程式在進行資料傳輸的時候,可能會選擇不同的協議。例如我們使用的瀏覽器就是使用的 TCP 協議,而像之前講過的 DNS 解析就用的 UDP 協議。
那資料在協議棧中到底經歷了什麼?才變成了一個一個的資料包?
就拿我們向伺服器傳送一個 HTTP 請求作為例子,我們知道 HTTP 請求中有:
請求行請求頭請求體HTTP 是屬於應用層的協議,而應用層還有很多其他的協議,每個協議所涉及到的資料也都不同,協議棧要怎麼去相容不同協議之間的資料呢?
答案是不做相容。對於協議棧來說,所有的資料都只不過是一堆二進位制序列。
那協議棧收到了這一堆二進位制序列之後是不是就直接交給網絡卡傳送了呢?
我都這麼問了,那顯然不是了...
其實協議棧在收到資料之後並不會馬上就會就傳送出去,而是會先寫入位於記憶體的 Buffer 中。那為啥不直接發出呢?
其實很簡單,假設你現在正在公交車的起始站,你覺得公交車會來一個人就立馬發車嗎?
顯然不是,它會等一段時間,有更多的乘客上車之後再發車。但是它又不能等太長的時間,不然後續站臺的乘客就會等的很久。
協議棧之所以不立即發出去,其實也是同樣的道理。其實這背後無非基礎兩種考慮:
資料的長度等待的時間應用層的程式傳送過來的資料可能長度都不太一樣,有的可能一個位元組一個位元組的發, 有的可能一次性就傳入所有的資料。
如果收到資料就傳送出去,會導致在網路中傳輸著很多小包,而這會降低網路傳輸的效率。
所以,協議棧在收到資料之後會等待一段時間,等資料達到一定量之後,再執行傳送操作。
但是,協議棧又不能等的太久是吧?等太久了你讓正在電腦面前操作的使用者情何以堪,這種傳送延遲會讓使用者體驗刷刷的往下掉。
但是吧,想做到對這兩者的平衡卻不是一件簡單的事。資料包太短,降低網路傳輸效率,等待太長時間,又會造成傳送延遲。所以協議棧索性就把控制權交給了應用程式。
應用程式可以自己控制到底採取哪種措施,例如我們常用的瀏覽器,因為和使用者實時的在進行互動,使用者對整個頁面的響應速度也相當敏感,所以一般都會採用直接傳送資料的方式,即使其資料並沒有達到「一定的量」
這一個「一定的量」到底是啥?
的確,上面都只說一定的量、一定的量,那這個量到底是多少?
要了解這個我們需要知道兩個引數,分別是:
MTU(Maximum Transmission Unit)最大傳輸單元MSS(Maximum Segment Size)最大分段大小MTU 其實就代表了上面途中資料包的最大長度,一般來說是 1500 位元組。而我們需要知道資料包是由以下部分組成的:
各種頭部資訊真實資料而從 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 的擁塞控制相關的東西。