今天咱們來總結學習一下TCP發送報文的相關知識,主要包括髮送報文的步驟,MSS,滑動窗口和Nagle算法。算法
該節主要根據陶輝大神的系列文章總結而來。以下圖所示,咱們一塊兒來看一下TCP發送報文時操做系統內核都作了那些事情。其中有些概念在接下來的小節中會介紹。緩存
首先,用戶程序在用戶態調用send
方法來發送一段較長的數據。而後send
函數調用內核態的tcp_sendmsg
方法進行處理。微信
主要注意的是,send
方法返回成功,內核也不必定真正將IP報文都發送到網絡中,也就是說內核發送報文和send
方法是不一樣步的。因此,內核須要將用戶態內存中的發送數據,拷貝到內核態內存中,不依賴於用戶態內存,使得進程能夠快速釋放發送數據佔用的用戶態內存。網絡
在拷貝過程當中,內核將待發送的數據,按照MSS來劃分紅多個儘可能接近MSS大小的分片,放到這個TCP鏈接對應的tcp_write_queue
發送隊列中。架構
內核中爲每一個TCP鏈接分配的內核緩存,也就是tcp_write_queue
隊列的大小是有限的。當沒有多餘的空間來複制用戶態的待發送數據時,就須要調用sk_stream_wait_memory
方法來等待空間,等到滑動窗口移動,釋放出一些緩存出來(收到發送報文相對應的ACK後,不須要再緩存該已發送出的報文,由於既然已經確認對方收到,就不須要重發,能夠釋放緩存)。tcp
當這個套接字是阻塞套接字時,等待的超時時間就是SO_SNDTIMEO
選項指定的發送超時時間。若是這個套接字是非阻塞套接字,則超時時間就是0。也就是說,sk_stream_wait_memory
對於非阻塞套接字會直接返回,並將 errno錯誤碼置爲EAGAIN。ide
咱們假定使用了阻塞套接字,且等待了足夠久的時間,收到了對方的ACK,滑動窗口釋放出了緩存。因此,能夠將剩下的用戶態數據都組成MSS報文拷貝到內核態的緩存隊列中。函數
最後,調用tcp_push
等方法,它最終會調用IP層的方法來發送tcp_write_queue
隊列中的報文。注意的是,IP層方法返回時,也不意味着報文發送了出去。學習
在發送函數處理過程當中,Nagle算法、滑動窗口、擁塞窗口都會影響發送操做。ui
咱們都知道TCP/IP架構有五層協議,低層協議的規則會影響到上層協議,好比說數據鏈路層的最大傳輸單元MTU和傳輸層TCP協議的最大報文段長度MSS。
數據鏈路層協議會對網絡分組的長度進行限制,也就是不能超過其規定的MTU,例如以太網限制爲1500字節,802.3限制爲1492字節。可是,須要注意的時,如今有些網卡具有自動分包功能,因此也能夠傳輸遠大於MTU的幀。
網絡層的IP協議試圖發送報文時,若報文的長度大於MTU限制,就會被分紅若干個小於MTU的報文,每一個報文都會有獨立的IP頭部。IP協議能自動獲取所在局域網的MTU值,而後按照這個MTU來分片。IP協議的分片機制對於傳輸層是透明的,接收方的IP協議會根據收到的多個IP包頭部,將發送方IP層分片出的IP包重組爲一個消息。
這種IP層的分片效率是不好的,由於首先作了額外的分片操做,而後全部分片都到達後,接收方纔能重組成一個包,其中任何一個分片丟失了,都必須重發全部分片。
因此,TCP層爲了不IP層執行數據報分片定義了最大報文段長度MSS。在TCP創建鏈接時會通知各自指望接收到的MSS的大小。
須要注意的是MSS的值是預估值。兩臺主機只是根據其所在局域網的計算MSS,可是TCP鏈接上可能會穿過許多中間網絡,這些網絡分別具備不一樣的數據鏈路層,致使問題。好比說,若中間途徑的MTU小於兩臺主機所在的網絡MTU時,選定的MSS仍然太大了,會致使中間路由器出現IP層的分片或者直接返回錯誤(設置IP頭部的DF標誌位)。
好比阿里中間件的這篇文章(連接不見的話,請看文末)所說,當上述狀況發生時,可能會致使卡死狀態,好比scp的時候進度卡着不懂,或者其餘更復雜操做的進度卡死。
IP層協議屬於不可靠的協議,IP層並不關心數據是否發送到了接收方,TCP經過確認機制來保證數據傳輸的可靠性。
除了保證數據一定發送到對端,TCP還要解決包亂序(reordering)和流控的問題。包亂序和流控會涉及滑動窗口和接收報文的out_of_order隊列,另外擁塞控制算法也會處理流控,詳情請看TCP擁塞控制算法簡介。
TCP頭裏有一個字段叫Window,又叫Advertised-Window,這個字段是接收端告訴發送端本身還有多少緩衝區能夠接收數據。因而發送端就能夠根據這個接收端的處理能力來發送數據,不然會致使接收端處理不過來。
咱們能夠將發送的數據分爲如下四類,將它們放在時間軸上統一觀察。
除了四種不一樣範疇的數據外,咱們能夠看到上邊的示意圖中還有三種窗口。
下面,咱們來看一下滑動窗口的滑動。下圖是滑動窗口滑動的示意圖。
當發送方收到發送數據的確認消息時,會移動發送窗口。好比上圖中,接收到36字節的確認,將其以前的5個字節都移除發送窗口,而後46-51的字節發出,最後將52到56的字節加入到可用窗口。
下面咱們來看一下總體的示意圖。
圖片來源爲tcpipguide.
client端窗口中不一樣顏色的矩形塊表明的含義和上邊滑動窗口示意圖中相同。咱們只簡單看一下第二三四步。接收端發送的TCP報文window爲260,表示發送窗口減小100,能夠發現黑色矩形縮短了,也就是發送窗口減小了100。而且ack爲141,因此發送端將140個字節的數據從發送窗口中移除,這些數據從Send But Not Yet Acknowledged變爲Sent and Acknowledged,也就是從藍色變成紫色。而後發送端發送180字節的數據,就有180字節的數據從Not Sent,Recipient Ready to Receive變爲Send But Not Yet Acknowledged,也就是從綠色變爲藍色。
上述滑動窗口會出現一種Silly Window Syndrome的問題,當接收端來不及取走Receive Windows裏的數據,會致使發送端的發送窗口愈來愈小。到最後,若是接收端騰出幾個字節並告訴發送端如今有幾個字節的window,而咱們的發送端會義無反顧地發送這幾個字節。
只爲了發送幾個字節,要加上TCP和IP頭的40多個字節。這樣,效率過低,就像你搬運物品,明明一次能夠所有搬完,可是卻恰恰一次只搬一個物品,來回搬屢次。
爲此,TCP引入了Nagle算法。應用進程調用發送方法時,可能每次只發送小塊數據,形成這臺機器發送了許多小的TCP報文。對於整個網絡的執行效率來講,小的TCP報文會增長網絡擁塞的可能。所以,若是有可能,應該將相臨的TCP報文合併成一個較大的TCP報文(固然仍是小於MSS的)發送。
Nagle算法的規則以下所示(可參考tcp_output.c文件裏tcp_nagle_check函數註釋):
當對請求的時延很是在乎且網絡環境很是好的時候(例如同一個機房內),Nagle算法能夠關閉。使用TCP_NODELAY套接字選項就能夠關閉Nagle算法
訂閱最新文章,歡迎關注個人微信公衆號
我的博客: Remcarpediem
我的微信公衆號: