網絡數據傳輸時操做系統幹了什麼?

前言

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

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

建立Socket

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

建立完成後,就會返回一個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服務器進行通信了。socket

這整個過程就像一個黑盒同樣,應用層和開發人員徹底無需干預。TCP協議的分層及操做系統提供的接口大大下降了開發人員的工做量,甚至無需深刻了解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
微信掃一掃二維碼關注訂閱號傑哥技術分享
出處:http://www.javashuo.com/article/p-qhnxsznv-nd.html 做者:傑哥很忙 本文使用「CC BY 4.0」創做共享協議。歡迎轉載,請在明顯位置給出出處及連接。

相關文章
相關標籤/搜索