TCP是如何保證可靠傳輸的?


相信大家都知道 TCP 是一個可靠傳輸的協議,那它是如何保證可靠的呢?

為了實現可靠性傳輸,需要考慮很多事情,例如數據的破壞、丟包、 重復以及分片順序混亂等問題。如不能解決這些問題,也就無從談起可靠傳輸。

那么,TCP 是通過序列號、確認應答、 重發控制、連接管理以及窗口控制等機制實現可靠性傳輸的。

重傳機制

TCP 實現可靠傳輸的方式之一,是通過序列號與確認應答。

在 TCP 中,當發送端的數據到達接收主機時,接收端主機會返回一個確認應答消息,表示已收到消息。

TCP 針對數據包丟失的情況,會用重傳機制解決。

接下來說說常⻅的重傳機制:超時重傳、 快速重傳、 SACK、 D-SACK
1.超時重傳
重傳機制的其中一個方式,就是在發送數據時,設定一個定時器,當超過指定的時間后,沒有收到對方的 ACK 確認應答報文,就會 重發該數據,也就是我們常說的超時重傳。
TCP 會在以下兩種情況發生超時 重傳: 數據包丟失、確認應答丟失
超時時間應該設置為多少呢?
RTT (Round-Trip Time 往返時延),就是數據從網絡一端傳送到另一端所需的時間,也就是包的往返時間
RTO (Retransmission Timeout 超時 重傳時間)
超時重傳時間 RTO 的值應該略大於報文往返 RTT 的值。

2.快速重傳

TCP 還有另外一種快速重傳(Fast Retransmit)機制,它不以時間為驅動,而是以數據驅動重傳。

 

在上圖,發送方發出了 1,2,3,4,5 份數據:

第一份 Seq1 先送到了,於是就 Ack 返回 2;
結果 Seq2 因為某些原因沒收到,Seq3 到達了,於是還是 Ack 回 2;
后面的 Seq4 和 Seq5 都到了,但還是 Ack 回 2,因為 Seq2 還是沒有收到;
發送端收到了三個 Ack = 2 的確認,知道了 Seq2 還沒有收到,就會在定時器過期之前,重傳丟失的 Seq2。

最后,收到了 Seq2,此時因為 Seq3,Seq4,Seq5 都收到了,於是 Ack 回 6 。

所以,快速 重傳的工作方式是當收到三個相同的 ACK 報文時,會在定時器過期之前, 重傳丟失的報文段。

快速 重傳機制只解決了一個問題,就是超時時間的問題,但是它依然面臨着另外一個問題。就是重傳的時候,是重 傳之前的一個,還是重傳所有的問題。
比如對於上面的例子,是 傳 Seq2 呢?還是 傳 Seq2、Seq3、Seq4、Seq5 呢?因為發送端並不清楚這連續的 三個 Ack 2 是誰傳回來的。

根據 TCP 不同的實現,以上兩種情況都是有可能的。可⻅,這是一把雙刃劍。 為了解決不知道該 傳哪些 TCP 報文,於是就有 SACK 方法。

 3.SACK

還有一種實現 重傳機制的方式叫: SACK ( Selective Acknowledgment 選擇性確認)。

這種方式需要在 TCP 頭部「選項」字段里加一個 SACK 的東⻄,它可以將緩存的地圖發送給發送方,這樣發送 方就可以知道哪些數據收到了,哪些數據沒收到,知道了這些信息,就可以只重傳丟失的數據。

如下圖,發送方收到了三次同樣的 ACK 確認報文,於是就會觸發快速 重發機制,通過 SACK 信息發現只有 200~299 這段數據丟失,則 發時,就只選擇了這個 TCP 段進行重復。

如果要支持 SACK ,必須雙方都要支持。在 Linux 下,可以通過 net.ipv4.tcp_sack 參數打開這個功能(Linux 2.4 后默認打開)。

 4.Duplicate SACK

Duplicate SACK 又稱 D-SACK ,其主要使用了 SACK 來告訴「發送方」有哪些數據被重復接收了。

 

滑動窗口

窗口大小就是指無需等待確認應答,而可以繼續發送數據的最大值。窗口的實現實際上是操作系統開辟的一個緩存空間,發送方主機在等到確認應答返回之前,必須在緩沖區中保留已發送的數據。如果按期收到確認應答,此時數據就可以從緩存區清除。

假設窗口大小為 3 個 TCP 段,那么發送方就可以「連續發送」 3 個 TCP 段,並且中途若有 ACK 丟失,可以 通過「下一個確認應答進行確認」。如下圖:

圖中的 ACK 600 確認應答報文丟失,也沒關系,因為可以通過下一個確認應答進行確認,只要發送方收到了 ACK 700 確認應答,就意味着 700 之前的所有數據「接收方」都收到了。這個模式就叫
累計確認或者累計應答。
窗口大小由哪一方決定?

TCP 頭里有一個字段叫 Window ,也就是窗口大小。 這個字段是接收端告訴發送端自己還有多少緩沖區可以接收數據。於是發送端就可以根據這個接收端的處理能力來發送數據,而不會導致接收

端處理不過來。所以,通常窗口的大小是由接收方的窗口大小來決定的。發送方發送的數據大小不能超過接收方的窗口大小,否則接收方就無法正常接收到數據。

發送方的滑動窗口
我們先來看看發送方的窗口,下圖就是發送方緩存的數據,根據處理的情況分成四個部分,其中深藍色方框是發送 窗口,紫色方框是可用窗口:
 
#1 是已發送並收到 ACK確認的數據:1~31 字節
#2 是已發送但未收到 ACK確認的數據:32~45 字節
#3 是未發送但總大小在接收方處理范圍內(接收方還有空間):46~51字節
#4 是未發送但總大小超過接收方處理范圍(接收方沒有空間):52字節以后
在下圖,當發送方把數據「全部」都一下發送出去后,可用窗口的大小就為 0 了,表明可用窗口耗盡,在沒收到 ACK 確認之前是無法繼續發送數據了。

 

在下圖,當收到之前發送的數據 32~36 字節的 ACK 確認應答后,如果發送窗口的大小沒有變化,則滑動窗口往 右邊移動 5 個字節,因為有 5 個字節的數據被應答確認,接下來 52~56 字節又變成了
可用窗口,那么后續也就 可以發送 52~56 這 5 個字節的數據了。
程序是如何表示發送方的四個部分的呢?
TCP 滑動窗口方案使用三個指針來跟蹤在四個傳輸類別中的每一個類別中的字節。其中兩個指針是絕對指針(指特 定的序列號),一個是相對指針(需要做偏移)。

 

SND.WND :表示發送窗口的大小(大小是由接收方指定的);
SND.UNA :是一個絕對指針,它指向的是已發送但未收到確認的第一個字節的序列號,也就是 #2 的第一 個字節。

SND.NXT :也是一個絕對指針,它指向未發送但可發送范圍的第一個字節的序列號,也就是 #3 的第一個 字節。

指向 #4 的第一個字節是個相對指針,它需要 SND.UNA 指針加上 SND.WND 大小的偏移 ,就可以指向 #4 的第一個字節了。

可用窗口大小 = SND.WND -(SND.NXT - SND.UNA)

接收方的滑動窗口

 接下來我們看看接收方的窗口,接收窗口相對簡單一些,根據處理的情況划分成三個部分:

#1 + #2 是已成功接收並確認的數據(等待應用進程讀取);

#3 是未收到數據但可以接收的數據;
#4 未收到數據並不可以接收的數據;

其中三個接收部分,使用兩個指針進行划分:
RCV.WND :表示接收窗口的大小,它會通告給發送方。

RCV.NXT :是一個指針,它指向期望從發送方發送來的下一個數據字節的序列號,也就是 #3 的第一個字 節。

指向 #4 的第一個字節是個相對指針,它需要 RCV.NXT 指針加上 RCV.WND 大小的偏移 ,就可以指向 #4 的第一個字節了。

 

流量控制

發送方不能無腦的發數據給接收方,要考慮接收方處理能力。
如果一直無腦的發數據給對方,但對方處理不過來,那么就會導致觸發 發機制,從而導致網絡流 的無端的浪費。
為了解決這種現象發生,TCP 提供一種機制可以讓「發送方」根據「接收方」的實際接收能力控制發送的數據量, 這就是所謂的流量控制
窗口關閉
在前面我們都看到了,TCP 通過讓接收方指明希望從發送方接收的數據大小(窗口大小)來進行流 控制。
如果窗口大小為 0 時,就會阻止發送方給接收方傳遞數據,直到窗口變為非 0 為止,這就是窗口關閉。
接收方向發送方通告窗口大小時,是通過 ACK 報文來通告的。

那么,當發生窗口關閉時,接收方處理完數據后,會向發送方通告一個窗口非 0 的 ACK 報文,如果這個通告窗口 的 ACK 報文在網絡中丟失了,那麻煩就大了

這會導致發送方一直等待接收方的非 0 窗口通知,接收方也一直等待發送方的數據,如不采取措施,這種相互等待 的過程,會造成了死鎖的現象 

TCP 是如何解決窗口關閉時,潛在的死鎖現象呢? 

為了解決這個問題,TCP 為每個連接設有一個持續定時器,只要 TCP 連接一方收到對方的零窗口通知,就啟動持 續計時器。

如果持續計時器超時,就會發送窗口探測 ( Window probe ) 報文,而對方在確認這個探測報文時,給出自己現在的接收窗口大小。

如果接收窗口仍然為 0,那么收到這個報文的一方就會 新啟動持續計時器; 如果接收窗口不是 0,那么死鎖的局面就可以被打破了。 

 

窗口探測的次數一般為 3 次,每次大約 30-60 秒(不同的實現可能會不一樣)。如果 3 次過后接收窗口還是 0 的 話,有的 TCP 實現就會發 RST 報文來中斷連接。 

 

擁塞控制

 為什么要有擁塞控制啊,不是有流量控制了嘛?

前面的流量控制是避免發送方的數據填滿接受方的緩存,但是並不知道網絡中發生了什么。

一般來說,計算機網絡都處在一個共享的環境,因此也有可能會因為其他主機之間的通信使得網絡擁堵。

在網絡出現擁堵時,如果繼續發送大量數據包,可能會導致數據包時延、丟失等,這時 TCP 就會重傳數據,但是 一重傳就會導致網絡的負擔更重,於是會導致更大的延遲以及更多的丟包,

這個情況就會進入惡性循環被不斷地放大.... 

為了在「發送方」調節所要發送數據的 ,定義了一個叫做「擁塞窗口」的概念。 

擁塞窗口 cwnd是發送方維護的一個的狀態變 ,它會根據網絡的擁塞程度動態變化的。 

我們在前面提到過發送窗口 swnd 和接收窗口 rwnd 是約等於的關系,那么由於加入了擁塞窗口的概念后,此時發送窗口的值是swnd = min(cwnd, rwnd),也就是擁塞窗口和接收窗口中的最小值 

擁塞窗口 cwnd 變化的規則: 

只要網絡中沒有出現擁塞, cwnd 就會增大; 

但網絡中出現了擁塞, cwnd 就減少; 

其實只要「發送方」沒有在規定時間內接收到 ACK 應答報文,也就是發生了超時重傳,就會認為網絡出現了擁塞 。

擁塞控制有哪些算法?

  • 慢啟動
  • 擁塞避免
  • 擁塞發生
  • 快速恢復

慢啟動

TCP 在剛建立連接完成后,首先是有個慢啟動的過程,這個慢啟動的意思就是一點一點的提高發送數據包的數 , 如果一上來就發大量的數據,這不是給網絡添堵嗎?

慢啟動的算法記住一個規則就行:當發送方每收到一個 ACK,擁塞窗口 cwnd 的大小就會加 1。

這里假定擁塞窗口 cwnd 和發送窗口 swnd 相等,下面舉個栗子:

連接建立完成后,一開始初始化 cwnd = 1 ,表示可以傳一個 MSS 大小的數據。

當收到一個 ACK 確認應答后,cwnd 增加 1,於是一次能夠發送 2 個

當收到 2 個的 ACK 確認應答后, cwnd 增加 2,於是就可以比之前多發2 個,所以這一次能夠發送 4 個

當這 4 個的 ACK 確認到來的時候,每個確認 cwnd 增加 1, 4 個確認 cwnd 增加 4,於是就可以比之前多發 4 個,所以這一次能夠發送 8 個。 

 

 

可以看出慢啟動算法,發包的個數是指數性的增⻓。 

那慢啟動漲到什么時候是個頭呢?

有一個叫慢啟動⻔限 ssthresh (slow start threshold)狀態變 。

當 cwnd < ssthresh 時,使用慢啟動算法。

當 cwnd >= ssthresh 時,就會使用「擁塞避免算法」。 

 

擁塞避免算法

前面說道,當擁塞窗口 cwnd 「超過」慢啟動⻔限 ssthresh 就會進入擁塞避免算法。 一般來說 ssthresh 的大小是 65535 字節。 那么進入擁塞避免算法后,它的規則是:每當收到一個

ACK 時,cwnd 增加 1/cwnd。

接上前面的慢啟動的栗子,現假定 ssthresh 為 8 :當 8 個 ACK 應答確認到來時,每個確認增加 1/8,8 個 ACK 確認 cwnd 一共增加 1,於是這一次能夠發送 9個 MSS 大小的數據,變成了線性增⻓。 

所以我們可以發現,擁塞避免算法就是將原本慢啟動算法的指數增長變成了線性增長,還是增長階段,但是增長速度慢了一些,就這么一直增長着,網絡就會慢慢進入了擁塞的狀況了,於是就會出現

丟包現象,這時就需要對丟失的數據包進行重傳,當觸發了重傳機制,也就進入了擁塞發生算法。

 

擁塞發生

當網絡出現擁塞,也就是會發生數據包重傳,重傳機制主要有兩種:

  • 超時重傳
  • 快速重傳

當發生了「超時重傳」,則就會使用擁塞發生算法。 這個時候,ssthresh 和 cwnd 的值會發生變化:

ssthresh 設為 cwnd/2 , cwnd 置為 1 

還有更好的方式,前面我們講過「快速 傳算法」。當接收方發現丟了一個中間包的時候,發送三次前一個包的 ACK,於是發送端就會快速地重傳,不必等待超時再重傳。 

TCP 認為這種情況不嚴 ,因為大部分沒丟,只丟了一小部分,則

cwnd = cwnd/2 ,也就是設置為原來的一半;

ssthresh = cwnd ;

進入快速恢復算法 ;

 

快速恢復

快速 傳和快速恢復算法一般同時使用,快速恢復算法是認為,你還能收到 3 個重復 ACK 說明網絡也不那么糟 糕,所以沒有必要像 RTO 超時那么強烈。

正如前面所說,進入快速恢復之前, cwnd 和 ssthresh 已被更新了: cwnd = cwnd/2 ,也就是設置為原來的一半;

ssthresh = cwnd ; 然后,進入快速恢復算法如下:

擁塞窗口 cwnd = ssthresh + 3 ( 3 的意思是確認有 3 個數據包被收到了); 傳丟失的數據包;
如果再收到重復的 ACK,那么 cwnd 增加 1;

如果收到新數據的 ACK 后,把 cwnd 設置為第一步中的 ssthresh 的值,原因是該 ACK 確認了新的數據,說 明從 duplicated ACK 時的數據都已收到,該恢復過程已經結束,

可以回到恢復之前的狀態了,也即再次進 入擁塞避免狀態; 

也就是沒有向超時重傳一夜回到解放前,而是還在比較高的值,后續呈線性增長。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM