一篇看懂【TCP協議】---TCP協議詳解(值得收藏)

一、TCP概念

  • TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接(連接導向)的、可靠的、 基於IP的傳輸層協議。
  • 首先來看看OSI的七層模型
  • 我們需要知道TCP工作在網絡OSI的七層模型中的第四層——傳輸層,IP在第三層——網絡層,ARP 在第二層——數據鏈路層;同時,我們需要簡單的知道,數據從
  • 應用層發下來,會在每一層都會加上頭部信息,進行 封裝,然後再發送到數據接收端。這個基本的流程你需要知道,就是每個數據都會經過數據的封裝和解封 裝的過程。
  • 在OSI七層模型中,每一層的作用和對應的協議如下:

二、TCP頭部結構和字段介紹

  • 從上面圖片可以看出,TCP協議是封裝在IP數據包中。

  • 下圖是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個標志位比特

窗口

  • TCP通過滑動窗口的概念來進行流量控制。設想在發送端發送數據的速度很快而接收端接收速度卻很慢的情況下,為瞭保證數據不丟失,顯然需要進行流量控制, 協調好
  • 通信雙方的工作節奏。所謂滑動窗口,可以理解成接收端所能提供的緩沖區大小。TCP利用一個滑動的窗口來告訴發送端對它所發送的數據能提供多大的緩 沖區。窗口大小為
  • 字節數起始於確認序號字段指明的值(這個值是接收端正期望接收的字節)。窗口大小是一個16bit字段,因而窗口大小最大為65535字節。

檢驗和

  • 檢驗和覆蓋瞭整個TCP報文段:TCP首部和數據。這是一個強制性的字段,一定是由發端計算和存儲,並由收端進行驗證。

緊急指針

  • 隻有當URG標志置1時緊急指針才有效。緊急指針是一個正的偏移量,和序號字段中的值相加表示緊急數據最後一個字節的序號。

三、TCP流量控制(滑動窗口協議)

  • TCP流量控制主要是針對接收端的處理速度不如發送端發送速度快的問題,消除發送方使接收方緩存溢出的可能性。
  • TCP流量控制主要使用滑動窗口協議,滑動窗口是接受數據端使用的窗口大小,用來告訴發送端接收端的緩存大小,以此可以控制發送端發送數據的大小,從而達到流量
  • 控制的目的。這個窗口大小就是我們一次傳輸幾個數據。對所有數據幀按順序賦予編號,發送方在發送過程中始終保持著一個發送窗口,隻有落在發送窗口內的幀才允許被發送;
  • 同時接收方也維持著一個接收窗口,隻有落在接收窗口內的幀才允許接收。這樣通過調整發送方窗口和接收方窗口的大小可以實現流量控制。
  • 我們可以通過下圖來分析:

  • 這裡面需要思考一個問題?
  • 第一步發送瞭[33, 43),如果這次發送[35, 45),那中間重疊部分不是發送瞭兩次,所以這裡要思考: 是全部重新發送還是隻發送接收端沒有收到的數據,如果全部發送那麼重復
  • 發送的數據接收端怎麼處理。這個下面快速重傳會講。
  • 這樣一直傳輸數據,直到數據發送完成。這麼一來就保證數據數據的可靠性,因為如果某數據沒有獲取到,那麼ack永遠不會跳過它。
  • 這裡也要思考一個問題,如果某一數據一隻沒有獲取到,總不能一直這樣堵塞在這裡吧,這裡就要講接下來有關堵塞的解決方法。

四、TCP擁塞控制

  • 流量控制是通過接收方來控制流量的一種方式;而擁塞控制則是通過發送方來控制流量的一種方式。
  • TCP發送方可能因為IP網絡的擁塞而被遏制,TCP擁塞控制就是為瞭解決這個問題(註意和TCP流量控制的區別)。
  • TCP擁塞控制的幾種方法:慢啟動,擁塞避免,快重傳和快恢復。
  • 這裡先理解一個概念: 擁塞窗口
  • 擁塞窗口:發送方維持一個叫做擁塞窗口 cwnd的狀態變量。擁塞窗口的大小取決於網絡的擁塞程度,並且動態變化。
  • 發送方的讓自己的發送窗口=min(cwnd,接受端接收窗口大小)。說明: 發送方取擁塞窗口與滑動窗口的最小值作為發送的上限。
  • 發送方控制擁塞窗口的原則是:隻要網絡沒有出現擁塞,擁塞窗口就增大一些,以便把更多的分組發送出去。但隻要網絡出現擁塞,擁塞窗口就減小一些,以減少
  • 註入到網絡中的分組數。
  • 下面將討論擁塞窗口cwnd的大小是怎麼變化的。

1、慢啟動

  • TCP在連接過程的三次握手完成後,開始傳數據,並不是一開始向網絡通道中發送大量的數據包。因為假如網絡出現問題,很多這樣的大包會積攢在路由器上,很容易導致網
  • 絡中路由器緩存空間耗盡,從而發生擁塞。因此現在的TCP協議規定瞭,新建立的連接不能夠一開始就發送大尺寸的數據包,而隻能從一個小尺寸的包開始發送,在發送和數據被
  • 對方確認的過程中去計算對方的接收速度,來逐步增加每次發送的數據量(最後到達一個穩定的值,進入高速傳輸階段。相應的,慢啟動過程中,TCP通道處在低速傳輸階段),
  • 以避免上述現象的發生。這個策略就是慢啟動。
  • 畫個簡單的圖從原理上粗略描述一下

  • 我們思考一個慢啟動引起的性能問題?
  • 在海量用戶高並發訪問的大型網站後臺,有一些基本的系統維護需求。比如遷移海量小文件,就是從一些機器拷貝海量小碎文件到另一些機器,來完成一些系統維護的基本需求。
  • 慢啟動為什麼會對拷貝海量小文件的需求造成重大性能損失?
  • 舉個簡單的例子,我們對每個文件都采用獨立的TCP連接來傳輸(循環使用scp拷貝就是這個例子的實際場景,很常見的用法)。那麼工作過程應該是,每傳輸一個文件建立一個連接,然後連接處於慢啟動階段,傳輸小文件,每個小文件幾乎都處於獨立連接的慢啟動階段被傳輸,這樣傳輸過程所用的TCP包的總量就會增多。更細致的說一說這個事,如果在慢啟動過程中傳輸一個小文件,我們可能需要2至3個小包,而在一個已經完成慢啟動的TCP通道中(TCP通道已進入在高速傳輸階段),我們傳輸這個文件可能隻需要1個大包。
  • 網絡拷貝文件的時間基本上全部消耗都在網絡傳輸的過程中(發數據過去等對端ACK,ACK確認歸來繼續再發,這樣的數據來回交互相比較本機的文件讀寫非常耗時間),撇開三次握手和四次握手那些包,如果文件的數量足夠大,這個總時間就會被放大到需求難以忍受的地步。
  • 因此,在遷移海量小文件的需求下,我們不能使用“對每個文件都采用獨立的TCP連接來傳輸(循環使用scp拷貝)“這樣的策略,它會使每個文件的傳輸都處於在一個獨立TCP的慢啟動階段。
  • 如何避免慢啟動,進而提升性能?
  • 很簡單,盡量把大量小文件放在一個TCP連接中排隊傳輸。起初的一兩個文件處於慢啟動過程傳輸,後續的文件傳輸全部處於高速通道中傳輸,用這樣的方式來減少發包的數目,進而降低時間消耗。同樣,實際上這種傳輸策略帶來的性能提升的功勞不僅僅歸於避免慢啟動,事實上也避免瞭大量的3次握手和四次握手,這個對海量小文件傳輸的性能消耗也非常致命。

2、擁塞避免

  • 先補充下: 慢啟動中擁塞窗口的cwnd值,開始是1,接下開是指數型增漲的。1、2、4、8、16…..這樣漲太快瞭吧。那麼就有瞭堵塞避免。
  • cwnd不能一直這樣無限增長下去,一定需要某個限制。TCP使用瞭一個叫慢啟動門限(ssthresh)的變量,一旦cwnd>=ssthresh(大多數TCP的實現,通常大小都是65536),慢啟動過程結束,擁塞避免階段開始;
  • 擁塞避免:cwnd的值不再指數級往上升,開始加法增加。此時當窗口中所有的報文段都被確認時,cwnd的大小加1,cwnd的值就隨著RTT開始線性增加,這樣就可以避免增長過快導致網絡擁塞,慢慢的增加調整到網絡的最佳值。(它邏輯很簡單就是到一定值後,cwnd不在是指數增長,而是+1增長。這樣顯然慢多瞭)。
  • 非ECN環境下的擁塞判斷,發送方RTO超時,重傳瞭一個報文段,它的邏輯如下:
  1. 把ssthresh降低為cwnd值的一半。
  2. 把cwnd重新設置為1。
  3. 重新進入慢啟動過程。

  • 上面的圖還是蠻好理解的。

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的重傳。
  • 對此有兩種選擇:
  1. 一種是僅重傳timeout的包。也就是第3份數據。
  2. 另一種是重傳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的實際的實現)。可見,這是一把雙刃劍。
  • 總結: 不管是超時重傳還是快速重傳確實能保證數據的可靠性,但它無法解決的問題就是:比如發送端發瞭1、2、3、4、5,而接收端收到瞭1、3、4、5,那麼這個時候它發送的ack是2。那麼發送端發送的是重傳#2呢還是重傳#2,#3,#4,#5的問題。如果在發送#2,#3,#4,#5,本身資源是一種浪費,因為接受方#3,#4,#5已經緩存下來,隻需#2,所以在發一遍是無意義的。

赞(0)