為了實現可靠性傳輸,需要考慮很多事情,例如數據的破壞、丟包、 重復以及分片順序混亂等問題。如不能解決這些問題,也就無從談起可靠傳輸。
重傳機制
TCP 實現可靠傳輸的方式之一,是通過序列號與確認應答。
TCP 針對數據包丟失的情況,會用重傳機制解決。
2.快速重傳

第一份 Seq1 先送到了,於是就 Ack 返回 2;
結果 Seq2 因為某些原因沒收到,Seq3 到達了,於是還是 Ack 回 2;
后面的 Seq4 和 Seq5 都到了,但還是 Ack 回 2,因為 Seq2 還是沒有收到;
發送端收到了三個 Ack = 2 的確認,知道了 Seq2 還沒有收到,就會在定時器過期之前,重傳丟失的 Seq2。
最后,收到了 Seq2,此時因為 Seq3,Seq4,Seq5 都收到了,於是 Ack 回 6 。
所以,快速 重傳的工作方式是當收到三個相同的 ACK 報文時,會在定時器過期之前, 重傳丟失的報文段。
根據 TCP 不同的實現,以上兩種情況都是有可能的。可⻅,這是一把雙刃劍。 為了解決不知道該 傳哪些 TCP 報文,於是就有 SACK 方法。
3.SACK
這種方式需要在 TCP 頭部「選項」字段里加一個 SACK 的東⻄,它可以將緩存的地圖發送給發送方,這樣發送 方就可以知道哪些數據收到了,哪些數據沒收到,知道了這些信息,就可以只重傳丟失的數據。
如下圖,發送方收到了三次同樣的 ACK 確認報文,於是就會觸發快速 重發機制,通過 SACK 信息發現只有 200~299 這段數據丟失,則 發時,就只選擇了這個 TCP 段進行重復。

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

TCP 頭里有一個字段叫 Window ,也就是窗口大小。 這個字段是接收端告訴發送端自己還有多少緩沖區可以接收數據。於是發送端就可以根據這個接收端的處理能力來發送數據,而不會導致接收
端處理不過來。所以,通常窗口的大小是由接收方的窗口大小來決定的。發送方發送的數據大小不能超過接收方的窗口大小,否則接收方就無法正常接收到數據。
#2 是已發送但未收到 ACK確認的數據:32~45 字節
#3 是未發送但總大小在接收方處理范圍內(接收方還有空間):46~51字節


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 的第一個字節了。
流量控制
那么,當發生窗口關閉時,接收方處理完數據后,會向發送方通告一個窗口非 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 時的數據都已收到,該恢復過程已經結束,
可以回到恢復之前的狀態了,也即再次進 入擁塞避免狀態;

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