本文內容以下:
1)TCP協議概念
2)TCP頭部結構和字段介紹
3)TCP流量控制
滑動窗口
4)TCP擁塞控制
慢啓動、擁塞避免、快重傳、快恢復html
有關TCP的三次握手四次揮手單獨寫了一篇博客:【TCP協議】---TCP三次握手和四次揮手程序員
有關TCP粘包和黏包,也單獨寫一篇博客,下一篇博客就寫有關粘包黏包問題。算法
TCP(Transmission Control Protocol 傳輸控制協議)是一種面向鏈接(鏈接導向)的、可靠的、 基於IP的傳輸層協議。緩存
首先來看看OSI的七層模型網絡
咱們須要知道TCP工做在網絡OSI的七層模型中的第四層——傳輸層,IP在第三層——網絡層,ARP 在第二層——數據鏈路層;同時,咱們須要簡單的知道,數據從併發
應用層發下來,會在每一層都會加上頭部信息,進行 封裝,而後再發送到數據接收端。這個基本的流程你須要知道,就是每一個數據都會通過數據的封裝和解封 裝的過程。 socket
在OSI七層模型中,每一層的做用和對應的協議以下:高併發
從上面圖片能夠看出,TCP協議是封裝在IP數據包中。post
下圖是TCP報文數據格式。TCP首部若是不計選項和填充字段,它一般是20個字節。性能
下面分別對其中的字段進行介紹:
源端口和目的端口
各佔2個字節,這兩個值加上IP首部中的源端IP地址和目的端IP地址惟一肯定一個TCP鏈接。有時一個IP地址和一個端口號也稱爲socket(插口)。
序號(seq)
佔4個字節,是本報文段所發送的數據項目組第一個字節的序號。在TCP傳送的數據流中,每個字節都有一個序號。例如,一報文段的序號爲300,並且數據共100字節,
則下一個報文段的序號就是400;序號是32bit的無符號數,序號到達2^32-1後從0開始。
確認序號(ack)
佔4字節,是指望收到對方下次發送的數據的第一個字節的序號,也就是指望收到的下一個報文段的首部中的序號;確認序號應該是上次已成功收到數據字節序號+1。
只有ACK標誌爲1時,確認序號纔有效。
數據偏移
佔4比特,表示數據開始的地方離TCP段的起始處有多遠。實際上就是TCP段首部的長度。因爲首部長度不固定,所以數據偏移字段是必要的。數據偏移以32位爲長度單位,
也就是4個字節,所以TCP首部的最大長度是60個字節。即偏移最大爲15個長度單位=1532位=154字節。
保留
6比特,供之後應用,如今置爲0。
6個標誌位比特
① URG:當URG=1時,註解此報文應儘快傳送,而不要按原本的列隊次序來傳送。與「緊急指針」字段共同應用,緊急指針指出在本報文段中的緊急數據的最後一個字節的序號,
使接管方能夠知道緊急數據共有多長。
② ACK:只有當ACK=1時,確認序號字段纔有效;
③ PSH:當PSH=1時,接收方應該儘快將本報文段當即傳送給其應用層。
④ RST:當RST=1時,表示出現鏈接錯誤,必須釋放鏈接,而後再重建傳輸鏈接。復位比特還用來拒絕一個不法的報文段或拒絕打開一個鏈接;
⑤ SYN:SYN=1,ACK=0時表示請求創建一個鏈接,攜帶SYN標誌的TCP報文段爲同步報文段;
⑥ FIN:發端完成發送任務。
窗口
TCP經過滑動窗口的概念來進行流量控制。設想在發送端發送數據的速度很快而接收端接收速度卻很慢的狀況下,爲了保證數據不丟失,顯然須要進行流量控制, 協調好
通訊雙方的工做節奏。所謂滑動窗口,能夠理解成接收端所能提供的緩衝區大小。TCP利用一個滑動的窗口來告訴發送端對它所發送的數據能提供多大的緩 衝區。窗口大小爲
字節數起始於確認序號字段指明的值(這個值是接收端正指望接收的字節)。窗口大小是一個16bit字段,於是窗口大小最大爲65535字節。
檢驗和
檢驗和覆蓋了整個TCP報文段:TCP首部和數據。這是一個強制性的字段,必定是由發端計算和存儲,並由收端進行驗證。
緊急指針
只有當URG標誌置1時緊急指針纔有效。緊急指針是一個正的偏移量,和序號字段中的值相加表示緊急數據最後一個字節的序號。
在看這裏,爲了更好的理解,建議先了解TCP三次握手,四次揮手。博客:【TCP協議】---TCP三次握手和四次揮手
TCP流量控制主要是針對接收端的處理速度不如發送端發送速度快的問題,消除發送方使接收方緩存溢出的可能性。
TCP流量控制主要使用滑動窗口協議,滑動窗口是接受數據端使用的窗口大小,用來告訴發送端接收端的緩存大小,以此能夠控制發送端發送數據的大小,從而達到流量
控制的目的。這個窗口大小就是咱們一次傳輸幾個數據。對全部數據幀按順序賦予編號,發送方在發送過程當中始終保持着一個發送窗口,只有落在發送窗口內的幀才容許被髮送;
同時接收方也維持着一個接收窗口,只有落在接收窗口內的幀才容許接收。這樣經過調整發送方窗口和接收方窗口的大小能夠實現流量控制。
咱們能夠經過下圖來分析:
1、發送方接收到了對方發來的報文 ack = 33, win = 10,知道對方收到了 33 號前的數據,如今指望接收 [33, 43) 號數據。那咱們開始發送[33, 43) 號的數據。
2、[33, 43) 號的數據你是已經發送了,但接受方並無接受到[36,37]數據。因此接收方發送回對報文段 A 的確認:ack = 35, win = 10。
3、發送方收到了 ack = 35, win = 10,對方指望接收 [35, 45) 號數據。那麼發送方在發送[35, 45) 。
這裏面須要思考一個問題?
第一步發送了[33, 43),若是此次發送[35, 45),那中間重疊部分不是發送了兩次,因此這裏要思考: 是所有從新發送仍是只發送接收端沒有收到的數據,若是所有發送那麼重複
發送的數據接收端怎麼處理。這個下面快速重傳會講。
4、接收方接收到了報文段 [35, 41),接收方發送:ack = 41, win = 10. (這是一個累積確認)
5、發送方收到了 ack = 41, win = 10,對方指望接收 [41, 51) 號數據。
6、.......
這樣一直傳輸數據,直到數據發送完成。這麼一來就保證數據數據的可靠性,由於若是某數據沒有獲取到,那麼ack永遠不會跳過它。
這裏也要思考一個問題,若是某一數據一隻沒有獲取到,總不能一直這樣堵塞在這裏吧,這裏就要講接下來有關堵塞的解決方法。
流量控制是經過接收方來控制流量的一種方式;而擁塞控制則是經過發送方來控制流量的一種方式。
TCP發送方可能由於IP網絡的擁塞而被遏制,TCP擁塞控制就是爲了解決這個問題(注意和TCP流量控制的區別)。
TCP擁塞控制的幾種方法:慢啓動,擁塞避免,快重傳和快恢復。
這裏先理解一個概念: 擁塞窗口
擁塞窗口:發送方維持一個叫作擁塞窗口 cwnd的狀態變量。擁塞窗口的大小取決於網絡的擁塞程度,而且動態變化。
發送方的讓本身的發送窗口=min(cwnd,接受端接收窗口大小)。說明: 發送方取擁塞窗口與滑動窗口的最小值做爲發送的上限。
發送方控制擁塞窗口的原則是:只要網絡沒有出現擁塞,擁塞窗口就增大一些,以便把更多的分組發送出去。但只要網絡出現擁塞,擁塞窗口就減少一些,以減小
注入到網絡中的分組數。
下面將討論擁塞窗口cwnd的大小是怎麼變化的。
TCP在鏈接過程的三次握手完成後,開始傳數據,並非一開始向網絡通道中發送大量的數據包。由於假如網絡出現問題,不少這樣的大包會積攢在路由器上,很容易致使網
絡中路由器緩存空間耗盡,從而發生擁塞。所以如今的TCP協議規定了,新創建的鏈接不可以一開始就發送大尺寸的數據包,而只能從一個小尺寸的包開始發送,在發送和數據被
對方確認的過程當中去計算對方的接收速度,來逐步增長每次發送的數據量(最後到達一個穩定的值,進入高速傳輸階段。相應的,慢啓動過程當中,TCP通道處在低速傳輸階段),
以免上述現象的發生。這個策略就是慢啓動。
畫個簡單的圖從原理上粗略描述一下
咱們思考一個慢啓動引發的性能問題?
在海量用戶高併發訪問的大型網站後臺,有一些基本的系統維護需求。好比遷移海量小文件,就是從一些機器拷貝海量小碎文件到另外一些機器,來完成一些系統維護的基本需求。
慢啓動爲何會對拷貝海量小文件的需求形成重大性能損失?
舉個簡單的例子,咱們對每一個文件都採用獨立的TCP鏈接來傳輸(循環使用scp拷貝就是這個例子的實際場景,很常見的用法)。那麼工做過程應該是,每傳輸一個文件創建一個
鏈接,而後鏈接處於慢啓動階段,傳輸小文件,每一個小文件幾乎都處於獨立鏈接的慢啓動階段被傳輸,這樣傳輸過程所用的TCP包的總量就會增多。更細緻的說一說這個事,若是在
慢啓動過程當中傳輸一個小文件,咱們可能須要2至3個小包,而在一個已經完成慢啓動的TCP通道中(TCP通道已進入在高速傳輸階段),咱們傳輸這個文件可能只須要1個大包。
網絡拷貝文件的時間基本上所有消耗都在網絡傳輸的過程當中(發數據過去等對端ACK,ACK確認歸來繼續再發,這樣的數據來回交互相比較本機的文件讀寫很是耗時間),撇開三次
握手和四次握手那些包,若是文件的數量足夠大,這個總時間就會被放大到需求難以忍受的地步。
所以,在遷移海量小文件的需求下,咱們不能使用「對每一個文件都採用獨立的TCP鏈接來傳輸(循環使用scp拷貝)「這樣的策略,它會使每一個文件的傳輸都處於在一個獨立TCP的慢啓
動階段。
如何避免慢啓動,進而提高性能?
很簡單,儘可能把大量小文件放在一個TCP鏈接中排隊傳輸。起初的一兩個文件處於慢啓動過程傳輸,後續的文件傳輸所有處於高速通道中傳輸,用這樣的方式來減小發包的數
目,進而下降時間消耗。一樣,實際上這種傳輸策略帶來的性能提高的功勞不只僅歸於避免慢啓動,事實上也避免了大量的3次握手和四次握手,這個對海量小文件傳輸的性能消耗
也很是致命。
先補充下: 慢啓動中擁塞窗口的cwnd值,開始是1,接下開是指數型增漲的。一、二、四、八、16.....這樣漲太快了吧。那麼就有了堵塞避免。
cwnd不能一直這樣無限增加下去,必定須要某個限制。TCP使用了一個叫慢啓動門限(ssthresh)的變量,一旦cwnd>=ssthresh(大多數TCP的實現,一般大小都是65536),慢
啓動過程結束,擁塞避免階段開始;
擁塞避免:cwnd的值再也不指數級往上升,開始加法增長。此時當窗口中全部的報文段都被確認時,cwnd的大小加1,cwnd的值就隨着RTT開始線性增長,這樣就能夠避免增加過
快致使網絡擁塞,慢慢的增長調整到網絡的最佳值。(它邏輯很簡單就是到必定值後,cwnd不在是指數增加,而是+1增加。這樣顯然慢多了)。
非ECN環境下的擁塞判斷,發送方RTO超時,重傳了一個報文段,它的邏輯以下:
1)把ssthresh下降爲cwnd值的一半。
2)把cwnd從新設置爲1。
3)從新進入慢啓動過程。
上面的圖仍是蠻好理解的。
TCP要保證全部的數據包均可以到達,因此,必須要有重傳機制。
注意: 接收端給發送端的Ack確認只會確認最後一個連續的包,好比,發送端發了1,2,3,4,5一共五份數據,接收端收到了1,2,因而回ack 3,而後收到了4(注意此時3沒收到)
此時的TCP會怎麼辦?咱們要知道,由於正如前面所說的,SeqNum和Ack是以字節數爲單位,因此ack的時候,不能跳着確認,只能確認最大的連續收到的包,否則,發送端
就覺得以前的都收到了。
3.1)超時重傳機制
一種是不回ack,死等3,當發送方發現收不到3的ack超時後,會重傳3。一旦接收方收到3後,會ack 回 4——意味着3和4都收到了。
可是,這種方式會有比較嚴重的問題,那就是由於要死等3,因此會致使4和5即使已經收到了,而發送方也徹底不知道發生了什麼事,由於沒有收到Ack,因此,發送方可能會
悲觀地認爲也丟了,因此有可能也會致使4和5的重傳。
對此有兩種選擇:
① 一種是僅重傳timeout的包。也就是第3份數據。
② 另外一種是重傳timeout後全部的數據,也就是第3,4,5這三份數據。
這兩種方式有好也有很差。第一種會節省帶寬,可是慢,第二種會快一點,可是會浪費帶寬,也可能會有無用功。但整體來講都很差。由於都在等timeout,timeout可能會很長。
3.2)快速重傳機制
因而,TCP引入了一種叫Fast Retransmit的算法,不以時間驅動,而以數據驅動重傳。也就是說,若是,包沒有連續到達,就ack最後那個可能被丟了的包,若是發送方連續收到
3次相同的ack,就重傳。Fast Retransmit的好處是不用等timeout了再重傳,而是隻是三次相同的ack就重傳。
好比:若是發送方發出了1,2,3,4,5份數據,第一份先到送了,因而就ack回2,結果2由於某些緣由沒收到,3到達了,因而仍是ack回2,後面的4和5都到了,可是仍是ack回2
由於2仍是沒有收到,因而發送端收到了三個ack=2的確認,知道了2尚未到,因而就立刻重轉2。而後,接收端收到了2,此時由於3,4,5都收到了,因而ack回6。示意圖以下
Fast Retransmit只解決了一個問題,就是timeout的問題,它依然面臨一個艱難的選擇,就是重轉以前的一個仍是重裝全部的問題。對於上面的示例來講,是重傳#2呢仍是重傳
#2,#3,#4,#5呢?由於發送端並不清楚這連續的3個ack(2)是誰傳回來的?也許發送端發了20份數據,是#6,#10,#20傳來的呢。這樣,發送端頗有可能要重傳從#2到
#20的這堆數據(這就是某些TCP的實際的實現)。可見,這是一把雙刃劍。
總結: 無論是超時重傳仍是快速重傳確實能保證數據的可靠性,但它沒法解決的問題就是:好比發送端發了一、二、三、四、5,而接收端收到了一、三、四、5,那麼這個時候它發送的
ack是2。那麼發送端發送的是重傳#2呢仍是重傳#2,#3,#4,#5的問題。若是在發送#2,#3,#4,#5,自己資源是一種浪費,由於接受方#3,#4,#5已經緩存下來,只需
#2,因此在發一遍是無心義的。
有關TCP協議固然遠遠不止於此,之後隨着對它認識愈來愈清楚以後,也會再次補充,或者在寫一篇。