一篇看懂【TCP協(xié)議】---TCP協(xié)議詳解(值得收藏)
一、TCP概念
TCP(Transmission Control Protocol 傳輸控制協(xié)議)是一種面向連接(連接導(dǎo)向)的、可靠的、 基于IP的傳輸層協(xié)議。
首先來看看OSI的七層模型

我們需要知道TCP工作在網(wǎng)絡(luò)OSI的七層模型中的第四層——傳輸層,IP在第三層——網(wǎng)絡(luò)層,ARP 在第二層——數(shù)據(jù)鏈路層;同時,我們需要簡單的知道,數(shù)據(jù)從
應(yīng)用層發(fā)下來,會在每一層都會加上頭部信息,進行 封裝,然后再發(fā)送到數(shù)據(jù)接收端。這個基本的流程你需要知道,就是每個數(shù)據(jù)都會經(jīng)過數(shù)據(jù)的封裝和解封 裝的過程。
在OSI七層模型中,每一層的作用和對應(yīng)的協(xié)議如下:

二、TCP頭部結(jié)構(gòu)和字段介紹
從上面圖片可以看出,TCP協(xié)議是封裝在IP數(shù)據(jù)包中。

下圖是TCP報文數(shù)據(jù)格式。TCP首部如果不計選項和填充字段,它通常是20個字節(jié)。

下面分別對其中的字段進行介紹:
源端口和目的端口
各占2個字節(jié),這兩個值加上IP首部中的源端IP地址和目的端IP地址唯一確定一個TCP連接。有時一個IP地址和一個端口號也稱為socket(插口)。
序號(seq)
占4個字節(jié),是本報文段所發(fā)送的數(shù)據(jù)項目組第一個字節(jié)的序號。在TCP傳送的數(shù)據(jù)流中,每一個字節(jié)都有一個序號。例如,一報文段的序號為300,而且數(shù)據(jù)共100字節(jié),
則下一個報文段的序號就是400;序號是32bit的無符號數(shù),序號到達2^32-1后從0開始。
確認序號(ack)
占4字節(jié),是期望收到對方下次發(fā)送的數(shù)據(jù)的第一個字節(jié)的序號,也就是期望收到的下一個報文段的首部中的序號;確認序號應(yīng)該是上次已成功收到數(shù)據(jù)字節(jié)序號+1。
只有ACK標(biāo)志為1時,確認序號才有效。
數(shù)據(jù)偏移
占4比特,表示數(shù)據(jù)開始的地方離TCP段的起始處有多遠。實際上就是TCP段首部的長度。由于首部長度不固定,因此數(shù)據(jù)偏移字段是必要的。數(shù)據(jù)偏移以32位為長度單位,
也就是4個字節(jié),因此TCP首部的最大長度是60個字節(jié)。即偏移最大為15個長度單位=1532位=154字節(jié)。
保留
6比特,供以后應(yīng)用,現(xiàn)在置為0。
6個標(biāo)志位比特
1、URG:當(dāng)URG=1時,注解此報文應(yīng)盡快傳送,而不要按本來的列隊次序來傳送。與“緊急指針”字段共同應(yīng)用,緊急指針指出在本報文段中的緊急數(shù)據(jù)的最后一個字節(jié)的序號, 使接管方可以知道緊急數(shù)據(jù)共有多長。?
2、ACK:只有當(dāng)ACK=1時,確認序號字段才有效;?
3、PSH:當(dāng)PSH=1時,接收方應(yīng)該盡快將本報文段立即傳送給其應(yīng)用層。?
4、RST:當(dāng)RST=1時,表示出現(xiàn)連接錯誤,必須釋放連接,然后再重建傳輸連接。復(fù)位比特還用來拒絕一個不法的報文段或拒絕打開一個連接;?
5、SYN:SYN=1,ACK=0時表示請求建立一個連接,攜帶SYN標(biāo)志的TCP報文段為同步報文段;?
6、FIN:發(fā)端完成發(fā)送任務(wù)。
窗口
TCP通過滑動窗口的概念來進行流量控制。設(shè)想在發(fā)送端發(fā)送數(shù)據(jù)的速度很快而接收端接收速度卻很慢的情況下,為了保證數(shù)據(jù)不丟失,顯然需要進行流量控制, 協(xié)調(diào)好
通信雙方的工作節(jié)奏。所謂滑動窗口,可以理解成接收端所能提供的緩沖區(qū)大小。TCP利用一個滑動的窗口來告訴發(fā)送端對它所發(fā)送的數(shù)據(jù)能提供多大的緩 沖區(qū)。窗口大小為
字節(jié)數(shù)起始于確認序號字段指明的值(這個值是接收端正期望接收的字節(jié))。窗口大小是一個16bit字段,因而窗口大小最大為65535字節(jié)。
檢驗和
檢驗和覆蓋了整個TCP報文段:TCP首部和數(shù)據(jù)。這是一個強制性的字段,一定是由發(fā)端計算和存儲,并由收端進行驗證。
緊急指針
只有當(dāng)URG標(biāo)志置1時緊急指針才有效。緊急指針是一個正的偏移量,和序號字段中的值相加表示緊急數(shù)據(jù)最后一個字節(jié)的序號。
三、TCP流量控制(滑動窗口協(xié)議)
TCP流量控制主要是針對接收端的處理速度不如發(fā)送端發(fā)送速度快的問題,消除發(fā)送方使接收方緩存溢出的可能性。
TCP流量控制主要使用滑動窗口協(xié)議,滑動窗口是接受數(shù)據(jù)端使用的窗口大小,用來告訴發(fā)送端接收端的緩存大小,以此可以控制發(fā)送端發(fā)送數(shù)據(jù)的大小,從而達到流量
控制的目的。這個窗口大小就是我們一次傳輸幾個數(shù)據(jù)。對所有數(shù)據(jù)幀按順序賦予編號,發(fā)送方在發(fā)送過程中始終保持著一個發(fā)送窗口,只有落在發(fā)送窗口內(nèi)的幀才允許被發(fā)送;
同時接收方也維持著一個接收窗口,只有落在接收窗口內(nèi)的幀才允許接收。這樣通過調(diào)整發(fā)送方窗口和接收方窗口的大小可以實現(xiàn)流量控制。
我們可以通過下圖來分析:

1.發(fā)送方接收到了對方發(fā)來的報文 ack = 33, win = 10,知道對方收到了 33 號前的數(shù)據(jù),現(xiàn)在期望接收 [33, 43) 號數(shù)據(jù)。那我們開始發(fā)送[33, 43) 號的數(shù)據(jù)。 2.[33, 43) 號的數(shù)據(jù)你是已經(jīng)發(fā)送了,但接受方并沒有接受到[36,37]數(shù)據(jù)。所以接收方發(fā)送回對報文段 A 的確認:ack = 35, win = 10。 3.發(fā)送方收到了 ack = 35, win = 10,對方期望接收 [35, 45) 號數(shù)據(jù)。那么發(fā)送方在發(fā)送[35, 45) 。
這里面需要思考一個問題?
第一步發(fā)送了[33, 43),如果這次發(fā)送[35, 45),那中間重疊部分不是發(fā)送了兩次,所以這里要思考: 是全部重新發(fā)送還是只發(fā)送接收端沒有收到的數(shù)據(jù),如果全部發(fā)送那么重復(fù)
發(fā)送的數(shù)據(jù)接收端怎么處理。這個下面快速重傳會講。
4.接收方接收到了報文段 [35, 41),接收方發(fā)送:ack = 41, win = 10. (這是一個累積確認) 5.發(fā)送方收到了 ack = 41, win = 10,對方期望接收 [41, 51) 號數(shù)據(jù)。 6. ? .......
這樣一直傳輸數(shù)據(jù),直到數(shù)據(jù)發(fā)送完成。這么一來就保證數(shù)據(jù)數(shù)據(jù)的可靠性,因為如果某數(shù)據(jù)沒有獲取到,那么ack永遠不會跳過它。
這里也要思考一個問題,如果某一數(shù)據(jù)一只沒有獲取到,總不能一直這樣堵塞在這里吧,這里就要講接下來有關(guān)堵塞的解決方法。
四、TCP擁塞控制
流量控制是通過接收方來控制流量的一種方式;而擁塞控制則是通過發(fā)送方來控制流量的一種方式。
TCP發(fā)送方可能因為IP網(wǎng)絡(luò)的擁塞而被遏制,TCP擁塞控制就是為了解決這個問題(注意和TCP流量控制的區(qū)別)。
TCP擁塞控制的幾種方法:慢啟動,擁塞避免,快重傳和快恢復(fù)。
這里先理解一個概念: 擁塞窗口
擁塞窗口:發(fā)送方維持一個叫做擁塞窗口 cwnd的狀態(tài)變量。擁塞窗口的大小取決于網(wǎng)絡(luò)的擁塞程度,并且動態(tài)變化。
發(fā)送方的讓自己的發(fā)送窗口=min(cwnd,接受端接收窗口大小)。說明: 發(fā)送方取擁塞窗口與滑動窗口的最小值作為發(fā)送的上限。
發(fā)送方控制擁塞窗口的原則是:只要網(wǎng)絡(luò)沒有出現(xiàn)擁塞,擁塞窗口就增大一些,以便把更多的分組發(fā)送出去。但只要網(wǎng)絡(luò)出現(xiàn)擁塞,擁塞窗口就減小一些,以減少
注入到網(wǎng)絡(luò)中的分組數(shù)。
下面將討論擁塞窗口cwnd的大小是怎么變化的。
1、慢啟動
TCP在連接過程的三次握手完成后,開始傳數(shù)據(jù),并不是一開始向網(wǎng)絡(luò)通道中發(fā)送大量的數(shù)據(jù)包。因為假如網(wǎng)絡(luò)出現(xiàn)問題,很多這樣的大包會積攢在路由器上,很容易導(dǎo)致網(wǎng)
絡(luò)中路由器緩存空間耗盡,從而發(fā)生擁塞。因此現(xiàn)在的TCP協(xié)議規(guī)定了,新建立的連接不能夠一開始就發(fā)送大尺寸的數(shù)據(jù)包,而只能從一個小尺寸的包開始發(fā)送,在發(fā)送和數(shù)據(jù)被
對方確認的過程中去計算對方的接收速度,來逐步增加每次發(fā)送的數(shù)據(jù)量(最后到達一個穩(wěn)定的值,進入高速傳輸階段。相應(yīng)的,慢啟動過程中,TCP通道處在低速傳輸階段),
以避免上述現(xiàn)象的發(fā)生。這個策略就是慢啟動。
畫個簡單的圖從原理上粗略描述一下

我們思考一個慢啟動引起的性能問題?
在海量用戶高并發(fā)訪問的大型網(wǎng)站后臺,有一些基本的系統(tǒng)維護需求。比如遷移海量小文件,就是從一些機器拷貝海量小碎文件到另一些機器,來完成一些系統(tǒng)維護的基本需求。
慢啟動為什么會對拷貝海量小文件的需求造成重大性能損失?
舉個簡單的例子,我們對每個文件都采用獨立的TCP連接來傳輸(循環(huán)使用scp拷貝就是這個例子的實際場景,很常見的用法)。那么工作過程應(yīng)該是,每傳輸一個文件建立一個連接,然后連接處于慢啟動階段,傳輸小文件,每個小文件幾乎都處于獨立連接的慢啟動階段被傳輸,這樣傳輸過程所用的TCP包的總量就會增多。更細致的說一說這個事,如果在慢啟動過程中傳輸一個小文件,我們可能需要2至3個小包,而在一個已經(jīng)完成慢啟動的TCP通道中(TCP通道已進入在高速傳輸階段),我們傳輸這個文件可能只需要1個大包。
網(wǎng)絡(luò)拷貝文件的時間基本上全部消耗都在網(wǎng)絡(luò)傳輸?shù)倪^程中(發(fā)數(shù)據(jù)過去等對端ACK,ACK確認歸來繼續(xù)再發(fā),這樣的數(shù)據(jù)來回交互相比較本機的文件讀寫非常耗時間),撇開三次握手和四次握手那些包,如果文件的數(shù)量足夠大,這個總時間就會被放大到需求難以忍受的地步。
因此,在遷移海量小文件的需求下,我們不能使用“對每個文件都采用獨立的TCP連接來傳輸(循環(huán)使用scp拷貝)“這樣的策略,它會使每個文件的傳輸都處于在一個獨立TCP的慢啟動階段。
如何避免慢啟動,進而提升性能?
很簡單,盡量把大量小文件放在一個TCP連接中排隊傳輸。起初的一兩個文件處于慢啟動過程傳輸,后續(xù)的文件傳輸全部處于高速通道中傳輸,用這樣的方式來減少發(fā)包的數(shù)目,進而降低時間消耗。同樣,實際上這種傳輸策略帶來的性能提升的功勞不僅僅歸于避免慢啟動,事實上也避免了大量的3次握手和四次握手,這個對海量小文件傳輸?shù)男阅芟囊卜浅V旅?/p>
2、擁塞避免
先補充下: 慢啟動中擁塞窗口的cwnd值,開始是1,接下開是指數(shù)型增漲的。1、2、4、8、16.....這樣漲太快了吧。那么就有了堵塞避免。
cwnd不能一直這樣無限增長下去,一定需要某個限制。TCP使用了一個叫慢啟動門限(ssthresh)的變量,一旦cwnd>=ssthresh(大多數(shù)TCP的實現(xiàn),通常大小都是65536),慢啟動過程結(jié)束,擁塞避免階段開始;
擁塞避免:cwnd的值不再指數(shù)級往上升,開始加法增加。此時當(dāng)窗口中所有的報文段都被確認時,cwnd的大小加1,cwnd的值就隨著RTT開始線性增加,這樣就可以避免增長過快導(dǎo)致網(wǎng)絡(luò)擁塞,慢慢的增加調(diào)整到網(wǎng)絡(luò)的最佳值。(它邏輯很簡單就是到一定值后,cwnd不在是指數(shù)增長,而是+1增長。這樣顯然慢多了)。
非ECN環(huán)境下的擁塞判斷,發(fā)送方RTO超時,重傳了一個報文段,它的邏輯如下:
把ssthresh降低為cwnd值的一半。
把cwnd重新設(shè)置為1。
重新進入慢啟動過程。

上面的圖還是蠻好理解的。
3、快速重傳
TCP要保證所有的數(shù)據(jù)包都可以到達,所以,必需要有重傳機制。
注意: ?接收端給發(fā)送端的Ack確認只會確認最后一個連續(xù)的包,比如,發(fā)送端發(fā)了1,2,3,4,5一共五份數(shù)據(jù),接收端收到了1,2,于是回ack 3,然后收到了4(注意此時3沒收到)此時的TCP會怎么辦?我們要知道,因為正如前面所說的,SeqNum和Ack是以字節(jié)數(shù)為單位,所以ack的時候,不能跳著確認,只能確認最大的連續(xù)收到的包,不然,發(fā)送端就以為之前的都收到了。
3.1)超時重傳機制
一種是不回ack,死等3,當(dāng)發(fā)送方發(fā)現(xiàn)收不到3的ack超時后,會重傳3。一旦接收方收到3后,會ack 回 4——意味著3和4都收到了。
但是,這種方式會有比較嚴重的問題,那就是因為要死等3,所以會導(dǎo)致4和5即便已經(jīng)收到了,而發(fā)送方也完全不知道發(fā)生了什么事,因為沒有收到Ack,所以,發(fā)送方可能會悲觀地認為也丟了,所以有可能也會導(dǎo)致4和5的重傳。
對此有兩種選擇:
一種是僅重傳timeout的包。也就是第3份數(shù)據(jù)。
另一種是重傳timeout后所有的數(shù)據(jù),也就是第3,4,5這三份數(shù)據(jù)。
這兩種方式有好也有不好。第一種會節(jié)省帶寬,但是慢,第二種會快一點,但是會浪費帶寬,也可能會有無用功。但總體來說都不好。因為都在等timeout,timeout可能會很長。
3.2)快速重傳機制
于是,TCP引入了一種叫Fast Retransmit的算法,不以時間驅(qū)動,而以數(shù)據(jù)驅(qū)動重傳。也就是說,如果,包沒有連續(xù)到達,就ack最后那個可能被丟了的包,如果發(fā)送方連續(xù)收到3次相同的ack,就重傳。Fast Retransmit的好處是不用等timeout了再重傳,而是只是三次相同的ack就重傳。
比如:如果發(fā)送方發(fā)出了1,2,3,4,5份數(shù)據(jù),第一份先到送了,于是就ack回2,結(jié)果2因為某些原因沒收到,3到達了,于是還是ack回2,后面的4和5都到了,但是還是ack回2因為2還是沒有收到,于是發(fā)送端收到了三個ack=2的確認,知道了2還沒有到,于是就馬上重轉(zhuǎn)2。然后,接收端收到了2,此時因為3,4,5都收到了,于是ack回6。
示意圖如下

Fast Retransmit只解決了一個問題,就是timeout的問題,它依然面臨一個艱難的選擇,就是重轉(zhuǎn)之前的一個還是重裝所有的問題。對于上面的示例來說,是重傳#2呢還是重傳#2,#3,#4,#5呢?因為發(fā)送端并不清楚這連續(xù)的3個ack(2)是誰傳回來的?也許發(fā)送端發(fā)了20份數(shù)據(jù),是#6,#10,#20傳來的呢。這樣,發(fā)送端很有可能要重傳從#2到#20的這堆數(shù)據(jù)(這就是某些TCP的實際的實現(xiàn))??梢?,這是一把雙刃劍。
總結(jié): 不管是超時重傳還是快速重傳確實能保證數(shù)據(jù)的可靠性,但它無法解決的問題就是:比如發(fā)送端發(fā)了1、2、3、4、5,而接收端收到了1、3、4、5,那么這個時候它發(fā)送的ack是2。那么發(fā)送端發(fā)送的是重傳#2呢還是重傳#2,#3,#4,#5的問題。如果在發(fā)送#2,#3,#4,#5,本身資源是一種浪費,因為接受方#3,#4,#5已經(jīng)緩存下來,只需#2,所以在發(fā)一遍是無意義的。
