前言:網絡知識很是的重要,若是你不是作程序的,那麼一些網絡常識仍是得知道的;而作程序的,就更不用說了,不只須要瞭解一些網絡知識,仍是知道其原理,若是不瞭解原理,不敢說他不是程序員,可是總缺了點意思,就像去北京沒去過長城同樣。html
網絡原理系列文章:程序員
1、五分鐘瞭解網絡鏈接(已完成)服務器
2、收發數據的原理(上)(已完成)網絡
3、收發數據的原理(下)(已完成)併發
4、收發數據的番外篇(未完成)app
由於網絡原理不是三言兩語能夠講完,若是讀者很忙,能夠直接拉到最底下,看總結,知道個大概,再回頭細讀此文章。感謝關注。廢話很少說,直接進入主題。在上篇咱們已經講了TCP收發數據的前兩步,接下來是最後兩步。socket
上篇講到控制流程從 connect 回到應用程序以後,就到了數據收發階段。 數據收發數據是從應用程序調用write將要發送的數據交給協議棧開始的,協議棧收到數據後執行發送操做,這一操做包含以下要點。post
首先,協議棧並不關心應用程序傳來的數據是什麼內容。應用程序調用write時會指定發送數據的長度,在協議棧看來,要發送的數據數據就是必定長度的二進制字節序列而已。大數據
其次並非一收到數據就立刻發送出去,而是會將數據存放在內部的發送緩衝區中,而且繼續等下一段數據。不過應用程序交給協議棧發送的數據長度是由應用程序自己決定,有些應用程序會一次性傳遞全部的數據,有些程序則會逐字節或者逐行傳遞數據。操作系統
總之,一次將多少數據交給協議棧是由應用程序決定的,協議棧沒有這個控制行爲。
協議棧之因此不一收到數據就發出去,是由於那樣可能會發送大量的小包,致使網絡效率降低。至於積累多少數據才發送,有如下兩個要素判斷。
第一,每一個網絡包能容納的數據長度。協議棧會根據一個叫作MTU的參數來進行判斷。MTU表示一個網絡包的最大長度,在以太網中通常是1500字節。MTU包含了頭部的總長度,因此MTU減去頭部長度纔是一個網絡包所能容納的最大數據長度,這一長度叫作MSS。當協議棧收到的長度大於或者接近MSS時發送出去,就很好的解決大量小包的問題。
MTU表示一個網絡包的最大長度,在以太網中通常是1500字節。MTU包含了頭部的總長度,MTU = MSS + 頭部,因此MSS是一個網絡包所能容納的最大數據長度。
第二,等待時間。當應用程序發送數據頻率不高的時候,協議棧收到的數據要接近MSS,可能要等很是久,而形成發送延遲,因此在這種狀況下,即時緩衝區的數據沒接到MSS,都發送出去。協議棧內部裏面有計時器,通過必定時間,就會把網絡包發送出去。
協議棧內部裏面有計時器,通過必定時間,就會把網絡包發送出去。
讀者能夠發現,其實這兩個判斷要素是相互矛盾的。若是長度優先,網絡效率會提升,但可能由於等待而產生髮送延遲;相反,時間優先,則會下降網絡效率,但延遲時間減小。因此這兩個要素要綜合考慮,以達到平衡。這個平衡由協議棧的開發者來決定,因此不一樣種類和版本的操做系統在相關操做上也就存在差別。固然應用程序在發送數據時,能夠指定發送選項,好比說讓網絡包直接發送,不用存在緩衝區了。
HTTP請求消息通常不會很長,一個網絡就可裝下,但若是要發送一張圖片或者發送一篇長文呢,發送緩衝區的數據確定超過MSS的長度。這時,咱們除了不等到後面的數據,還要對現有數據進行拆分,拆分的每塊數據會放進每一個單獨的網絡包。
上一篇也講過,發送數據前,要在每一塊數據添加TCP頭部,並根據套接字中包含的通訊對象的信息(發送方和接收方的端口號),而後交給IP模塊處理髮送操做,IP模塊會在每一個網絡包前面添加IP頭部和以太網頭部,具體操做,後面再講。
網絡以及其餘環境很複雜,收發數據時,不免會在發送中出現錯誤,因此須要檢測和補償機制。
網絡包發往服務器,須要確認對方是否收到網絡包,對方沒收到時及時重發。那麼確認原理是什麼?
TCP模塊在拆分數據時,會算好每一塊數據至關於從頭開始的第幾個字節,接下來在發送此塊數據,會將算好的字節數寫在TCP頭部中,上一篇中說到的seq做用就在這裏。而後告知接收方數據長度,可是數據長度不是經過TCP頭部傳輸,由於接收方能夠經過整個網絡包的長度減去頭部長度得出。因此,咱們能夠知道發送的數據是從第幾個字節開始,長度是多少。
經過上面兩個數值,接收方還能夠檢查收到的網絡包有沒遺漏。好比:上次接收到第1120字節,若是接下來收到序號是第1121的包,則表示沒有遺漏。收到第2200字節,則有包遺漏了。若是確認沒有遺漏,接收方會將到目前爲止接收到的數據長度加起來,計算出一共已經收到了多少個字節,而後將這個數值寫入TCP頭部的ACK號中發送給發送方(TCP的seq和ack號計算方法),返回ACK號這一操做稱做確認響應。
有個須要注意的是,seq序號不是從1開始,由於從1開始,很容易被猜到,被攻擊者發動攻擊。因此seq序號初始值是用隨機數算出來,開始收發數據前須要告知通訊對象序號初始值。上文講到鏈接過程當中,有一個將SYN控制位設爲1併發送給服務器的操做,就是在這一步將序號的初始值告知對方的。實際上,在將SYN設爲1的同時,還須要同時設置序號字段的值,而這裏的值就是初始值。
經過seq序號和ACK號能夠確認數據,咱們前面只考慮了單向傳輸,但TCP數據收發是雙向的,因此客戶端向服務器發送數據,服務器也會向客戶端發送。因此收發雙方都須要計算序號,而且在鏈接過程當中相互告訴對方本身計算的序號初始值。
上圖表示了實際的工做過程。首先,客戶端在鏈接時須要計算出序號初始值並告知服務器(①)。接下來,服務器會經過初始值計算出ACK號並返回給客戶端(②)。初始值有可能在通訊中丟失,因此服務器須要返回ACK號給客戶端做爲確認。由於數據傳輸是雙向,服務器也須要告知客戶端它計算出來的序號初始值,並將其發給客戶端(②)。接下來,客戶端也會計算出ACK號告知服務器,已經收到了其發來的初始值(③)。到此,鏈接操做工做完成。接下來到收發操做工做,數據收發工做能夠雙向同時進行。客戶端向服務器發送請求,序號也會跟隨數據一塊兒發送(④),服務器收到數據返回ACK號(⑤)。同理,服務器向客戶端發送數據(⑥⑦)。
在獲得對方確認以前,發送過的網絡包都會保存在緩衝區中,若是出現丟包現象,也就是通訊對象沒有返回ACK,協議棧中的TCP模塊從新發送這些包。
經過「seq」和「ACK」能夠確認對方是否收到網絡包。
返回ACK號的等待時間(也叫超時時間),當網絡繁忙時會發生擁塞,這時須要把等待時間設置長點,不然重發包了,上次須要返回的ACK號纔來,這樣會致使原本就擁塞的網絡更加要命。若是設置等待時間過長,也不行,重傳包會有很大延遲。這又要找一個時間平衡,真難!因此TCP採用了動態調整等待時間的方法。這個等待時間根據ACK號返回所需的時間來判斷的。具體來講,TCP會在發送數據的過程當中,不斷的測量ACK號的返回時間,若是ACK號返回很慢,則延長等待時間,相反,若是返回很快,則縮短等待時間。
每發送一個網絡包,就等到一個ACK號返回,這個很容易理解,可是在等待ACK返回這段時間,若是什麼都不作,就很是浪費。爲了減小浪費,TCP採用滑動窗口管理數據發送和ACK號的操做。所謂滑動窗口,就是在發送一個包,不等待ACK號返回,直接發送後續的一系列包。
可是這樣有可能出現如下問題,在不返回ACK號的時候,就連續發送包,可能致使發送包的頻率超過接收方處理能力的狀況。具體來講,接收方TCP接收到包,會先將數據存放到接收緩衝區中。而後,接收方須要計算ACK號,將數據塊組裝起來還原成本來的數據並傳遞給應用程序,若是該操做未完成,又有下一個包到來,一樣是存入接收緩衝區中,若是包到來速率比將數據塊組裝數據並傳給應用程序速率快,緩衝區數據就會越積越多,最後溢出,接收方就收不到後面的包了。因此,接收方須要告訴發送方本身最多能接收多少數據,而後發送方根據這個值對數據發送進行控制,這個最大值稱爲窗口大小。這就是滑動窗口方式的基本思路。
可以接收的最大數據量稱爲窗口大小,它屬於TCP調優的一個重要參數
前面說過窗口大小就是最大接收量,當接收的數據存入緩衝區中,不必立刻向發送方更新窗口大小,更新窗口大小時機應該是接收方從緩衝區中取出數據傳遞給應用程序的時候,由於這時,緩衝區中數據減小,剩餘的空間變大,理應告訴發送方。
接收方收到數據,確認內容沒有問題,就應該向發送方返回ACK號。假設ACK包是一個包,而更新窗口大小又是另一個包,這樣可能會收到一個包的狀況下,接收方須要向發送方返回兩個包。這樣一來,接收方發給發送方的包就太多了,致使網絡效率降低。
因此,若是在等待發送ACK的時候,恰好也要更新窗口大小,就能夠把這兩個包合併成一個包發送,從而減小的包的數量。當須要連續發送多個ACK號,也能夠減小包的數量,這是由於ACK號表示的是已經收到的數據量,也就是說,它是告訴發送方目前已接收的數據最後位置在哪裏,由於當須要連續發送ACK號時,只要發送最後一個ACK號就能夠了。同理,當須要連續發送多個窗口更新也能夠減小包的數量。
客戶端委託協議棧發送請求後,等待服務端返回的消息,調用read程序來獲取響應消息。和發送數據同樣,接收數據也須要將數據暫存到接收緩衝區中。具體操做以下,協議棧嘗試從接收緩衝區取出數據並傳遞給應用程序,但這個時候可能響應消息還沒返回,因此接收操做就無法繼續。那麼,協議棧會將應用程序的委託,也就是從緩衝區取數據的工做暫時掛起,等響應消息到達再繼續接收操做。注意,這裏只是掛起這項工做,協議棧並無中止工做,還會處理好多其餘的工做。
應用程序在發送數據和接收數據都依賴協議棧。
協議棧接收數據會先將數據放入緩衝區,而後將數據塊按順序鏈接,還原成原始數據,最後將數據交給應用程序。具體來講,協議棧會將接收方的數據複製到應用程序指定的內存地址中,而後將控制流程交給應用程序,同時,協議棧還要找到合適時機告訴發送方更新窗口大小。
應用程序接收數據,其判斷數據被所有接收完成,則這個時間就是收發數據結束的時間。協議棧在設計上容許通訊雙方的任意一方先發起斷開過程。大部分程序向服務器發送請求消息,服務器再返回響應消息,這時收發數據的過程就所有結束了,服務器一方會先發起斷開過程。也有一些程序是發完數據就先發起斷開過程。
協議棧在設計上容許通訊雙方的任意一方先發起斷開過程,具體哪方先斷開,由那方的程序決定。
咱們以常見的服務器斷開講解。首先,服務器一方的程序會調用Socket庫的 close 程序。而後,服務器的協議棧會生成包含斷開信息的 TCP 頭部,具體來講就是將控制位的 FIN 比特設爲1。接下來,協議棧會委託IP模塊向客戶端發送數據。同時,服務器的套接字中也會記錄下斷開操做的相關信息。
客戶端收到服務器發來的 FIN 爲 1 的TCP頭部時(①),客戶端協議棧會將本身的套接字標記進入斷開操做狀態。而後,爲了告知服務器已經收到 FIN 的包,客戶端會向服務器返回一個 ACK 號(②)。這些操做完成後,就等待應用程序來取數據了。
過了一會,應用程序就回來調用 read 來讀取數據。這時,協議棧不會嚮應用程序傳遞數據,而是會告知應用程序來自服務器的數據已經所有收到,客戶端收到所有數據,也會調用 close 結束數據收發操做,這時客戶端的協議棧也會和服務器同樣,生成一個FIN比特爲1的TCP包,而後委託IP模塊發送給服務器(③)。隔一段時間,服務器就會返回ACK號(④)。到此,客戶端和服務器的通訊所有結束。
有沒有記到前面說過,通訊雙方在鏈接階段中間相似有一條管道,準備鏈接時,咱們創建,如今收發數據結束,咱們理應要刪除它,其實也就是刪除這條虛擬管道的兩方套接字。
通訊結束以後,咱們要刪除套接字,不過,套接字不會當即被刪除,而是會等待一段時間以後再被刪除。等待一段時間是爲了防止誤操做,引發誤操做的緣由不少,好比說: 一、客戶端發送FIN 二、服務器返回ACK號 三、服務器發送FIN 四、客戶端發送ACK號
若是最後客戶端返回的ACK號丟失了,服務器沒有接受到ACK號,它可能會從新發送一次FIN。若是這個時候,客戶端的套接字已經刪除,那麼套接字中保存的開工至信息也跟着消失,套接字對應的端口號就會被釋放出來。這時,若是別的應用程序建立套接字,新套接字恰好被分配了同一個端口號,而服務器重發的FIN正好到達,這個時候,FIN就會錯誤的跑到新套接字裏面,新套接字就開始執行斷開操做了。因此不立刻刪除套接字,就是因爲這樣。
客戶端的端口號是從空閒的端口號中隨意選擇的。
等待多長時間才刪除套接字,這得看包重傳的操做方式。網絡包丟失以後會進行重傳,這操做通常要持續幾分鐘。若是重傳了幾分鐘以後依然無效,則中止重傳。因此通常等待幾分鐘以後再刪除套接字。
TCP收發數據的總體流程分爲如下三個部分。
收發數據三個步驟開始前的操做是建立套接字,應用程序調用Socket庫的一個程序組件socket程序申請建立套接字,以後協議棧去執行操做。
1、鏈接操做。建立完套接字,就準備鏈接通訊對象。首先,客戶端會生成一個SYN爲1的TCP包併發給服務器。這個TCP包的頭部包含了客戶端向服務器發送數據時使用的seq(初始序號),以及服務器發送數據給客戶端須要用到的窗口大小。這個包到達服務器後,服務器會返回一個SYN爲1的TCP包,這個TCP包一樣包含着序號和窗口大小,此外還包含表示已經收到客戶端發來的TCP包的ACK號。過段時間,客戶端會返回ACK號,表示已經收到服務器發送的TCP包。
2、收發操做。不一樣應用程序可能會有些異同。通常。客戶端會向服務器發送請求消息。TCP會將數據拆分紅不少個網絡包分別發送出去。每一個包的TCP頭部都包含這序號,表示當前發送的是第幾個字節數據。服務器收到包後,會返回ACK號,必定時間後也會返回更新窗口大小的包。固然通訊是雙向的,服務器也會向客戶端發送數據,也是相似的流程。
3、斷開操做。通常,服務器會先發起斷開過程。服務器先發一個 FIN 爲1的TCP包給客戶端,客戶端返回 ACK號做爲確認收到。客戶端收到所有數據,也會生成一個 FIN 比特爲1的TCP包,發送給服務器,服務器也返回ACK號,等待一段時間後,套接字會被刪除。到此,客戶端和服務器的通訊所有結束。
參考文獻:
網絡是怎樣鏈接的
歡迎關注技術公衆號「程序員大咖秀」