網絡數據傳輸時操作系統干了什么?


前言

最近在整理網絡抓包分析相關的資料,同時又在閱讀《網絡是怎樣連接的》。上一篇從網絡協議層對設備連網的過程和發送數據的過程進行了探討。本篇討論的是TCP協議的數據收發的過程。

在討論本篇文章時,假設讀者對TCP協議有一定了解。

創建Socket

由於TCP協議是需要建立連接的,在建立連接之前,需要先初始化Socket。在初始化Socket時,操作系統分配一片內存空間保存該Socket的必要信息。這些信息除了傳輸數據時需要TCP頭部的信息以外,還包括操作系統TCP協議需要用的SO_REUSEADDRTCP_NODELAY等TCP控制參數。

創建完成后,就會返回一個Socket描述符。這個描述符相當於一個唯一的Socket識別號,通過這個描述符就能從操作系統獲取到Socket實體對象。

建立連接

我們知道TCP建立連接三次握手實際是為了交換客戶端與服務端必要的信息,或者可以稱之為協商。主要協商的信息包括雙方的IP和端口號、接收緩沖區大小、以及其他TCP控制參數(如ECN:顯式擁塞通告,SACK:選擇確認選項等)。

在創建Socket時,還不知道需要和誰傳輸數據,僅初始化了一個Socket結構。在建立連接時,我們需要告訴底層操作系統,需要連接的目標地址。就好比寄快遞時,我們需要在快遞單寫上收件人的地址和以及收件人信息。

初始化緩沖區

在執行收發數據前,我們需要分配一個空間用於臨時存放數據,這個空間被稱為緩沖區。接收數據有接收數據緩沖區,發送數據有發送數據緩沖區。這兩個緩沖區也是在建立連接的時候分配的。

DNS解析器和ARP協議

操作系統支持我們直接傳遞一個明確的IP地址或者傳入一個域名。如果傳入的是域名,操作系統會調用DNS解析器。DNS解析器會通過DNS協議去查詢域名對應的IP,這個過程就是我們上一篇文章將的DNS協議部分。

當然如果我們還需要知道DNS的MAC地址,如果操作系統底層的ARP緩存沒有DNS的IP所對應的MAC地址記錄,還需要通過ARP協議進行廣播獲取DNS服務器的IP所對應的MAC地址。當獲取到該MAC地址后,操作系統就會將DNS的IP和MAC地址保存到ARP緩存中,下一次再調用DNS解析器時,就可以直接與DNS服務器進行通訊了。

這整個過程就像一個黑盒一樣,應用層和開發人員完全無需干預。TCP協議的分層及操作系統提供的接口大大降低了開發人員的工作量,甚至無需深入了解TCP協議就能開發出一套網絡通訊程序。就好比快遞員只要會開車,無需了解汽車內部結構一樣。

三次握手

當我們獲取到了需要連接的目的地址的真實IP時,就可以建立TCP連接了。建立連接需要三次握手,過程如下。

20200803181342.png

上圖看着是非常簡單,交互3次成功建立連接。實際這個過程中還有許多細節。在發送第一個SYN包以前(客戶端發送到服務端的第一個請求,TCP協議的Flags字段中將SYC設置成了1)。操作系統需要獲取到TCP頭部所需要的控制信息。
20200803101701.png

在建立連接時,操作系統需要從動態端口池中獲取一個可用的端口,將端口號信息保存到Socket對象的內存中。然后將頭部的控制位SYN設置為1,表示建立連接。接着還需要獲取操作系統的接收緩沖區大小,將其保存到TCP頭部的窗口大小字段中,當然發送序號和確認序號目前都為0。

當TCP頭部信息填寫完成后,操作系統就將TCP協議的數據傳遞給IP模塊。IP模塊執行網絡包發送之前會對數據包填充IP頭部,若有需要則對數據包進行分片,這個過程后面在討論。

獲取發送方IP

若客戶端只有一個網卡,發送方IP就是這個網卡的IP。但是如果有多個網卡,那么操作系統就需要選擇一個合適的網卡作為發送網卡。IP模塊通過路由表來判斷哪條路由能夠匹配,從而選擇一條合適的路由,最后就可以知道應該選擇哪個網卡。獲取該網卡的IP保存到IP頭部的源IP地址中,將目標IP保存到IP頭部的目標IP地址中。IP模塊添加IP頭部完成之后,繼續添加MAC頭部(MAC頭部實際也是IP協議添加的)。

獲取目的MAC地址

IP模塊需要獲取到目的設備的MAC地址。和前面DNS討論的一樣,若ARP緩存中有記錄,無需查找,若沒有記錄則會通過ARP協議查找目的設備的MAC地址,如果目的設備與當前設備在同一個局域網內,則會獲取到目的設備的MAC地址。如果目的設備與當前設備不在一個局域網,則會查找到下一個路由器的MAC地址。具體MAC地址是誰的IP模塊並不關心,他的任務就是獲取到目的MAC地址並將其填入到MAC頭部。

從理論上來說MAC地址是屬於以太網的范疇,不應該有IP模塊負責,但實際上,網卡只負責發送和接收而不參與拼包拆包,這樣就能夠兼容各種類型的包。

發送方的MAC地址就是網卡的MAC地址,這個值是在網卡生產時寫入到網卡的ROM中的,只要將其讀取出來填入到MAC頭部的發送端MAC地址字段中即可。

經過以上步驟之后,一個完整的建立連接的SYN包就創建完成了。接下來就要將這個包發送到對端。發送SYN的過程和發送數據的過程一樣,具體的發送邏輯在發送數據一節在做具體說明。

客戶端發送SYN包給服務端時,服務端會返回SYN+ACK包,在這個包中也會將自己的接收緩沖區的大小填寫到TCP頭部的窗口大小字段中。這樣客戶端發送數據時就可以盡量避免發送的在途包超過接收方的接收緩沖區導致丟包重傳。

當連接建立完成時,接下來雙方就可以收發數據了。

發送數據

在討論網卡發送數據之前,我們先看看應用層調用發送數據時發生了什么。

應用程序調用操作系統發送數據的接口時,並不是直接將數據發送到出去,而是先將數據保存到socket的發送緩沖區中。具體何時發送數據TCP協議棧決定的。

當發送緩沖區有數據要發送時,首先TCP協議棧會根據MSS對需要發送的數據進行分段。分段的目的是避免數據超過MTU大小導致數據被IP層分片。

MSS(Maximum Segment Size)表示最大段長度,MTU(Maximum Transmission Unit)表示最大傳輸單元。MTU是以太網是一個網絡包的最大長度,以太網中默認是1500。當數據包超過MTU時,IP模塊就會對數據進行分片,每一片都會有自己的TCP頭部和IP頭部。

MTU=MSS+IP頭部+TCP頭部

Nagle算法

當Socket配置了Nagle算法時,當Socket的發送緩沖區短時間內有大量的小包(數據長度小於MSS),則操作系統不會立即為每個包添加TCP頭部和IP頭部,而是會嘗試等待一段時間以便一次性將多個數據進行合並成一個大包發送。這樣能夠提升帶寬利用率,但是也可能會造成數據的短暫延遲發送。因此大多數情況下,我們通常會禁用Nagle算法。

TCP VS UDP

眾所周知UDP性能比TCP性能高得多,主要原因就是TCP為了保證可靠性做了大量的工作,如建立連接、重傳、擁塞避免、慢啟動等。但是為什么許多協議仍然選擇TCP而不是UDP呢?一個重要的原因就是傳輸小包數據適合使用UDP,比如DNS協議、DHCP協議。這些協議能夠保證數據在以太網傳輸時小於MTU避免被IP層分片。在復雜的網絡環境中,無法得知中間某個設備的配置,若數據超過設備MTU而設備又設置了DF標記(Don't Frgment,不分片)時就會將數據包丟棄。大多數應用層協議的數據很可能會超過MTU,使用TCP協議就可以通過分段來避免IP層分片。TCP分段數據丟失時TCP有超時重傳、快速重傳等機制保證數據的可靠性。而IP層沒有這個機制,同時IP層分片后即使一個片丟失也必須重傳所有分片。而TCP層數據丟失僅需重傳丟失的數據即可(SACK機制)。

網卡發送數據

現在需要發送的數據包已經加上了TCP頭部、IP頭部以及MAC頭部。接下來就可以將數據交給網卡將數據轉換為電信號或光信號在網線上傳輸了。IP模塊執行完成之后就會調用網卡驅動執行發送數據,網卡驅動從IP模塊獲取到數據包后,會將其復制到網卡的緩沖區中,然后會在包的開頭和結尾添加報頭和起始幀分界符,在包尾添加用於檢測錯誤的幀校驗序列(FCS)。

20200803151858.png

報頭是由共計56個比特的01,起始幀分界符是10101011,網卡通過報頭和起始幀分界符來判斷幀的位置。具體如何判斷這里不做具體討論。

20200803163804.png

ACK

當發送到對端時,並沒有結束,TCP協議通過ACK確認機制保證數據可靠。當接收端收到數據時,需要返回一個ACK包給客戶端來通知自己收到的數據包長度。發送數據時TCP首部有幾個關鍵參數,SEQ表示發送的數據起始偏移量,ACK表示確認收到對端的數據長度。當客戶端發送的SEQ=1461,數據長度是1460時,服務端若確認已收到SEQ小於1461以前所有的包和當前包時,就需要返回一個ACK包,該包的TCP頭部的ACK值為SEQ+數據長度,也就是2921。這樣客戶端就能確認服務端是否收到了數據,如果沒有收到數據,可通過重傳和快速重傳等機制重傳數據。具體重傳邏輯這里不進行詳細討論。

接收數據

接下來我們看接收數據時發生了什么。當網卡接收到網絡上的數據時,會將數據從電信號或光信號轉換為數字信號(也就是0和1),當網卡判斷到包尾時,會通過CRC算法計算出錯誤校驗碼和包尾的FCS進行對比,若不一致,則說明數據傳輸時發生了錯誤,則丟棄,客戶端最終通過重傳機制重發。若FCS校驗通過,還會校驗一下數據包的接收方MAC地址和當前網卡的MAC地址是否一致,若不一致則會丟棄,如果一致則會將數據包存放到網卡的接收緩沖區中。然后就會通過計算機的中斷機制通知計算機收到了數據。

中斷

首先,網卡向擴展總線中的中斷信號線發送信號,該信號線通過計算機中的中斷控制器連接到CPU。當產生中斷信號時,CPU會暫時掛起正在處理的任務,切換到操作系統中的中斷處理程序。(由於中斷例程的優先級較高,如果不先處理網卡緩沖區的數據,網卡緩沖區的數據很快就會滿,從而造成丟包)然后,中斷處理程序會調用網卡驅動,控制網卡執行相應的接收操作。中斷是有編號的,網卡在安裝的時候就在硬件中設置了中斷號,在中斷處理程序中則將硬件的中斷號和相應的驅動程序綁定。因此哪個網卡收到了數據就會調用哪個中斷處理程序從而調用到對應的網卡驅動。

網卡驅動被中斷處理程序調用后,就會從網卡的緩沖區中獲取到接收到的數據包,並通過MAC頭部判斷網絡層的協議棧(目前通常是TCP/IP協議),網卡驅動就會把包交給對應的協議棧處理。從這里可以看到網卡並不關心數據包的內容是什么,它只需要校驗數據是發給自己的,而且數據傳輸過程中沒有錯誤,就可以放入到緩沖區中。

值得一提的是,從網卡驅動緩沖區獲取接收的數據包是一個I/O操作,現代操作系統通常不會讓CPU直接從網卡緩沖區拷貝數據(這樣會占用大量的CPU執行I/O操作),而是向網卡的DMA控制器發送一個讀指令,DMA控制器會從網卡緩沖區讀取數據到內存中,當DMA控制器讀取數據完成時也會通過中斷通知CPU執行后續操作,這樣就能極大的提升CPU的使用效率。

對於I/O中斷可以看下我另一篇博客I/O中斷原理

IP模塊接收數據

當IP模塊獲取到接收的數據時,首先要判斷接收者是不是自己。因此會獲取到目的IP地址和當前IP地址比較是否一致。如果不一致,若服務器配置了路由功能則會轉發到目標IP,否則IP模塊就會通過ICMP消息將錯誤告知對方並丟棄數據包。如果收到的IP和當前IP一致,則需要進行一個分片重組過程。首先判斷數據包是否發生了分片。如果發生了分片,則需要將分片包還原。分片包的IP頭部的唯一標識符是一樣的,每個分片包會有自己的分片偏移量。

20200803153929.png

當接收到所有分片后就可以進行重組還原成一個完整包,IP模塊的任務就結束了,接下來就將數據包交給TCP模塊。TCP模塊會根據收方和發送方的IP和端口查找到對應的套接字,並根據套接字的狀態執行響應的操作。

TCP模塊接收數據

若接收到的是應用程序數據,則返回ACK,然后將數據放入socket的接收緩沖區中,等待應用來讀取(如果是異步IO則會發送一個完成通知到完成隊列,從而觸發應用程序讀取數據);如果是連接或關閉連接的控制包,則會返回對應的響應控制包(如SYN或FIN等),並告知應用程序的連接和關閉連接的操作狀態。

順便提一下,若操作系統開啟了延遲確認功能,則會延遲一段時間(windows下時200ms)返回ACK。

斷開連接

TCP斷開連接通過四次揮手,由於TCP協議是全雙工的,因此雙方互相發送FIN包以及返回ACK包,斷開雙向的連接。TCP協議也支持只斷開單向連接,被稱為半連接。Windows和Linux許多管道機制都是通過socket的半連接實現的。

客戶端再關閉連接后並不會立即釋放socket資源,而是進入TIME_WAIT狀態等待2MSL時間以后再釋放客戶端的Socket資源。

1個MSL是2分鍾,所以也就是4分鍾。

主要原因是客戶端的最后一個ACK可能丟包,此時服務端會重傳FIN包,若客戶端不等待一段時間才釋放資源而是立即釋放。新創建的Socket可能恰好又使用了相同的斷開,這時候接收到了服務端的FIN包就會立即錯誤的關閉了新的連接。

結語

通過兩篇文章分別從網絡協議和操作系統的協議棧2個方面的處理過程對網絡數據傳輸的過程進行了討論。一些更細節的東西本篇並沒有說明,比如路由器、交換機等網絡傳輸的具體過程。網卡內部各模塊的處理細節等。網卡更細的內容大部分開發者無需了解,但是操作系統協議棧的處理過程能夠幫助開發者理解自己所開發的網絡應用到底是如何處理的。

參考文獻

  1. 《網絡是怎樣連接的》
  2. 《Wireshark數據包分析實戰詳解》
  3. I/O中斷原理

20191127212134.png
微信掃一掃二維碼關注訂閱號傑哥技術分享
出處:https://www.cnblogs.com/Jack-Blog/p/13426728.html
作者:傑哥很忙
本文使用「CC BY 4.0」創作共享協議。歡迎轉載,請在明顯位置給出出處及鏈接。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM