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