數(shù)據(jù)在網(wǎng)絡中是如何傳輸?shù)?/h1>
交互過程如下圖所示:
套接字如何創(chuàng)建的
協(xié)議棧內(nèi)部結構
如上圖所示,整個請求交互過程分為了幾個部分,首先最上層就是應用程序,接著往下是 Socket 庫。
再下面就是操作系統(tǒng)的內(nèi)部了,這里面就包括了協(xié)議棧,協(xié)議棧上半部分為 TCP 和 UDP ,它們都是負責數(shù)據(jù)的收發(fā)。
只是一個需要 連接,一個不需要連接可以直接收發(fā)數(shù)據(jù),這兩者的詳細區(qū)別我會在后期文章單獨講解,這里大家先了解下就行。
協(xié)議棧的下半部分是 IP 協(xié)議,用來真正將數(shù)據(jù)轉(zhuǎn)變?yōu)榫W(wǎng)絡包進行實際數(shù)據(jù)傳送的媒介。
IP 下面就是網(wǎng)卡驅(qū)動程序,用來控制網(wǎng)卡硬件。
認識套接字
在協(xié)議棧內(nèi)部有一塊用來存放控制信息的內(nèi)存空間,這里面記錄了需要連接的對象 IP 地址、端口號、進行狀態(tài)等信息。
而套接字本身其實只是一個概念,實際并沒有這樣一個東西,這個概念如果非要賦予它一個實體,那控制信息可以認為就是它的實體。
在發(fā)送數(shù)據(jù)時,我們需要看下套接字要進行連接的對象 IP 地址和端口號;發(fā)送數(shù)據(jù)之后,套接字里面會記錄發(fā)送數(shù)據(jù)經(jīng)過了多長時間,如果發(fā)送收到響應,也會進行記錄。
我們來實際看下 套接字 都有哪些信息,可以在你電腦的控制臺輸入 netstat 命令進行查詢:
- Proto: 表示協(xié)議類型。這里是 tcp ,如果用到了 udp 就會顯示為 udp。
- Local Address : 本機的 IP 地址。
- Foreign Address : 通信對象的 IP 地址
- state : 通信狀態(tài)。ESTABLISHED 表示完成連接 ,CLOSE_WAIT 表示等待關閉,還有一個狀態(tài)也很常見,LISTENING:等待對方連接。
當瀏覽器通過 Socket 庫向協(xié)議棧發(fā)出 socket 調(diào)用時,協(xié)議棧就會根據(jù)申請執(zhí)行創(chuàng)建套接字的操作。
協(xié)議棧首先會分配一個存放套接字的內(nèi)存空間,然后往里面存入控制信息,這樣套接字就創(chuàng)建好了。
連接服務器
創(chuàng)建好套接字后,瀏覽器會調(diào)用 connect ,協(xié)議棧就會將本地的套接字和服務器的套接字進行連接。
連接就是通信雙方互相交換控制信息,連接操作所交換的控制信息是根據(jù)通信規(guī)則來確定的,只要雙方根據(jù)規(guī)則進行連接,就能建立起連接關系,完成數(shù)據(jù)收發(fā)的準備。
控制信息
控制信息一般可以分為兩類,一類是客戶端和服務器相互聯(lián)系時交換的控制信息,這個信息是兩者建立連接、數(shù)據(jù)收發(fā)、斷開連接整個通信過程都需要的信息。
一般這些內(nèi)容是通過 TCP 協(xié)議進行定義的。這些信息會被添加進網(wǎng)絡包的開頭,因此也叫作頭部,以太網(wǎng)和 IP 協(xié)議也有自己的控制信息,這個信息也叫頭部,為了進行區(qū)分,我們分別叫作 TCP 頭部、以太網(wǎng)頭部、IP 頭部。
這里羅列了部分 TCP 頭部的信息,僅供參考。
控制信息還有一類,是保存在套接字里的,應用程序傳遞的信息和從通信對象接受的信息都會保存在這里,以及數(shù)據(jù)收發(fā)操作的執(zhí)行狀態(tài)也會在這里面。
連接操作的實際過程
連接操作的第一步就是在 TCP 模塊處創(chuàng)建表示連接控制信息的頭部。當 TCP 頭部創(chuàng)建好后,TCP 模塊會將信息傳遞給 IP 模塊委托其進行發(fā)送。
IP 執(zhí)行發(fā)送后,網(wǎng)絡包會通過網(wǎng)絡到達服務器,服務器上的 IP 模塊將接收到的數(shù)據(jù)傳遞給 TCP 模塊,TCP 模塊根據(jù)頭部信息找到對應的套接字,套接字中會寫入相應的信息,然后將狀態(tài)改為正在連接。
于此同時,在返回響應時,會將 ACK 控制位設為 1,代表已接收到網(wǎng)絡包。服務器 TCP 模塊會將響應消息通過 IP 模塊向客戶端做出響應。
客戶端接收到響應后,其 IP 模塊將信息傳遞給 TCP 模塊,然后通過 TCP 頭部信息確認連接是否成功,SYN 等于 1 就代表成功,客戶端還會將 ACK 設置為1 并發(fā)回給服務器,服務器收到這個包后才算連接操作真正的完成。
建立連接后,就可以隨時進行收發(fā)數(shù)據(jù)了,在調(diào)用 close 之前,連接會一直存在。
收發(fā)數(shù)據(jù)
收發(fā)數(shù)據(jù)的觸發(fā)操作是應用程序發(fā)起的,通過調(diào)研 write,指定發(fā)送數(shù)據(jù)的長度。
一般當協(xié)議棧接受到數(shù)據(jù)時可能并不會馬上發(fā)出去,而是放在發(fā)送緩沖區(qū)中,為什么要這樣做呢?
有些程序可能一次性會傳所有數(shù)據(jù),但有些程序會逐行傳遞,在這種情況下,如果收到數(shù)據(jù)就發(fā)送,可能會造成發(fā)送大量小包數(shù)據(jù),導致效率低下。
至于需要積累多少數(shù)據(jù)才發(fā)送一般是根據(jù)兩方面因素來考量,一個是每個網(wǎng)絡包的數(shù)據(jù)長度,還有一個緯度是處理時間。
網(wǎng)絡包容納的數(shù)據(jù)長度
首先介紹下兩個名詞:
MTU: 一個網(wǎng)絡包的最大長度,以太網(wǎng)中一般是1500字節(jié),是包含頭部的總長度。
MSS: 除去頭部后,一個網(wǎng)絡包所有容納的數(shù)據(jù)最大長度。
處理時間
當一個應用程序發(fā)送數(shù)據(jù)的頻率不高時,如果每次都需要等到長度達到 MSS 才發(fā)送,就會造成等待時間過長。
為了解決這種情況,協(xié)議棧會有一個計時器,如果達到一定時間,即使還遠未達到 MSS 長度,也會把網(wǎng)絡包發(fā)送出去。
ACK 機制確認網(wǎng)絡包接收情況
當客戶端向服務端發(fā)送數(shù)據(jù)時,TCP會將數(shù)據(jù)的字節(jié)數(shù)算好寫在 TCP 頭部,同時會生成一個隨機數(shù) 當作 ACK 一并發(fā)送給服務端,服務端接受后就會根據(jù)實際收到的長度和TCP頭部給的長度做對比,來確保數(shù)據(jù)沒有遺漏。
同時客戶端還需要告知服務端是從哪個字節(jié)開始發(fā)送的,而我們的 ACK是個隨機值,這時候我們就需要通過 SYN 控制位設置為1發(fā)送給服務器,這樣服務器就知道其初始是從哪個字節(jié)開始發(fā)送的。
接受方收到數(shù)據(jù)后,如果數(shù)據(jù)沒問題,就需要告知發(fā)送方收到了多少數(shù)據(jù),也是通過 ACK 號的操作來返回的,這個 ACK 的值就是一共接收了多少字節(jié)。
通過這種機制,我們就可以確認接收方是否正確收到數(shù)據(jù),如果沒有準確收到,就可以重新發(fā)送網(wǎng)絡包。
無論網(wǎng)絡發(fā)生何種錯誤,我們就都可以發(fā)現(xiàn)并采取補救措施。
窗口滑動
一般如果我們每發(fā)送一個網(wǎng)絡包就等待 ACK 返回確認后再發(fā)送下一個包,這個等待 ACK 的時間啥都不做就會很浪費。
窗口滑動的概念就是每次發(fā)送一個網(wǎng)絡包,不會等 ACK 返回就會繼續(xù)發(fā)送下一個包,減少等待時間的浪費。
但這種方式也會存在問題,假如發(fā)送方不斷發(fā)送數(shù)據(jù)給接收方,接收方第一個數(shù)據(jù)還沒處理完,第二個數(shù)據(jù)就來了,這些來不及處理的數(shù)據(jù)會進入接收緩沖區(qū),數(shù)據(jù)會不斷增多,就會造成溢出。避免這種方式的處理是通過接收方告知發(fā)送方自己最大能接收多少數(shù)據(jù),發(fā)送方會根據(jù)這個值對發(fā)送的數(shù)據(jù)進行控制。
刪除套接字
當我們數(shù)據(jù)收發(fā)完成后,就會啟動斷開機制,以 Web 為例,收發(fā)數(shù)據(jù)結束時,服務器會發(fā)起斷開過程,會調(diào)用 Socket 庫的 close 程序,服務器協(xié)議棧會生成一個包含斷開信息的 TCP 頭部,就是將 FIN 比特設置為1。協(xié)議棧會委托 IP 模塊向客戶端發(fā)送數(shù)據(jù)。
當客戶端接收到 FIN 為 1 的 TCP 頭部時,客戶端協(xié)議棧會將自己的套接字標記為進入斷開操作狀態(tài),然后告知服務器已經(jīng)收到 FIN 為 1的包,客戶端會向服務器返回一個 ACK 號。
UDP 協(xié)議收發(fā)操作
之前我們都是以 TCP 協(xié)議講解的數(shù)據(jù)收發(fā)操作,可以看出整個流程下來其實是挺復雜的,但是有時候可能我們并不需要這么復雜的安全校驗,UDP 就可以滿足一些簡單的數(shù)據(jù)收發(fā)。例如像我們之前提到的 向 DNS 服務器查詢 IP 地址,我們就是用的 UDP 協(xié)議。
UDP 沒有 TCP 的接收確認、窗口等機制,在收發(fā)數(shù)據(jù)之前是不需要進行交換控制信息,不需要進行連接操作。
接收數(shù)據(jù)也很簡單,只需要根據(jù) IP 頭部中的接收方和發(fā)送方 IP 地址,以及 UDP 頭部中的接收方和發(fā)送方端口號,找到對應的套接字然后將數(shù)據(jù)交給相應的應用程序即可。