重傳機制
針對資料包丟失的情況,會用重傳機制解決。
超時重傳
在傳送資料時,設定一個定時器,當超過指定的時間後,沒有收到對方的ACK
確認應答報文,就會重發該資料,也就是我們常說的超時重傳。TCP 會在以下兩種情況發生超時重傳:資料包丟失,確認應答丟失
缺點:
-
當超時時間 RTO 較大時,重發就慢,丟了老半天才重發,沒有效率,效能差;
-
當超時時間 RTO 較小時,會導致可能並沒有丟就重發,於是重發的就快,會增加網路擁塞,導致更多的超時,更多的超時導致更多的重發。
所以超時重傳時間 RTO 的值應該略大於報文往返 RTT 的值。
快速重傳
TCP 還有另外一種快速重傳(Fast Retransmit)機制,它不以時間為驅動,而是以資料驅動重傳。
假設傳送方發出了 1,2,3,4,5 份資料,但是第二個資料丟失,1,3,4,5到了接收端只會回覆ack=2,當3,4,5都到達後,傳送者會收到3個ack=2的確認,知道了 Seq2 還沒有收到,就會在定時器過期之前,重傳丟失的 Seq2。
但是它依然面臨著另外一個問題。就是重傳的時候,是重傳一個,還是重傳所有的問題。
舉個例子,假設傳送方發了 6 個資料,編號的順序是 Seq1 ~ Seq6 ,但是 Seq2、Seq3 都丟失了,那麼接收方在收到 Seq4、Seq5、Seq6 時,都是回覆 ACK2 給傳送方,但是傳送方並不清楚這連續的 ACK2 是接收方收到哪個報文而回復的, 那是選擇重傳 Seq2 一個報文,還是重傳 Seq2 之後已傳送的所有報文呢。
- 如果只選擇重傳 Seq2 一個報文,那麼重傳的效率很低。因為對於丟失的 Seq3 報文,還得在後續收到三個重複的 ACK3 才能觸發重傳。
- 如果選擇重傳 Seq2 之後已傳送的所有報文,雖然能同時重傳已丟失的 Seq2 和 Seq3 報文,但是 Seq4、Seq5、Seq6 的報文是已經被接收過了,對於重傳 Seq4 ~Seq6 折部分資料相當於做了一次無用功,浪費資源。
SACK選擇性確認
這種方式需要在 TCP 頭部「選項」欄位里加一個 SACK
的東西,它可以將已收到的資料的資訊傳送給「傳送方」,這樣傳送方就可以知道哪些資料收到了,哪些資料沒收到,知道了這些資訊,就可以只重傳丟失的資料。
滑動視窗
為什麼引入視窗
TCP 是每傳送一個資料,都要進行一次確認應答。當上一個資料包收到了應答了, 再傳送下一個。但這種方式的缺點是效率比較低的。為解決這個問題,TCP 引入了視窗這個概念。
原理
假設視窗大小為 3
個 TCP 段,那麼傳送方就可以「連續傳送」 3
個 TCP 段,並且中途若有 ACK 丟失,可以透過「下一個確認應答進行確認」。意味著 所有資料「接收方」都收到了。這個模式就叫累計確認或者累計應答。
視窗大小誰決定
TCP 頭裡有一個欄位叫 Window
,也就是視窗大小。這個欄位是接收端告訴傳送端自己還有多少緩衝區可以接收資料。所以,通常視窗的大小是由接收方的視窗大小來決定的。
傳送方:
接收方:
流量控制
作業系統與滑動視窗的關係
-
當應用程式沒有及時讀取快取時:假設客戶端與服務端在進行正常資料發收,但是服務端非常繁忙,每次應用程序只讀取了部分位元組,於是就有剩餘的位元組佔著緩衝區,於是視窗就會收縮,當進行多次這種情況後,視窗最後會變為0,也就是發生了視窗關閉。
-
當服務端系統資源緊張時,作業系統可能會直接減少了接收緩衝區大小:服務端因繁忙,作業系統就把接收快取減少位元組。這時應用程式又無法及時讀取快取資料,那麼這時候就有嚴重的事情發生了,會出現資料包丟失的現象。
為了防止這種情況發生,TCP 規定是不允許同時減少快取又收縮視窗的,而是採用先收縮視窗,過段時間再減少快取,這樣就可以避免了丟包情況。
視窗關閉
如果視窗大小為 0 時,就會阻止傳送方給接收方傳遞資料,直到視窗變為非 0 為止,這就是視窗關閉。
當發生視窗關閉時,接收方處理完資料後,會向傳送方通告一個視窗非 0 的 ACK 報文,如果這個通告視窗的 ACK 報文在網路中丟失了,那麻煩就大了。這會導致傳送方一直等待接收方的非 0 視窗通知,接收方也一直等待傳送方的資料,造成了死鎖的現象。
為了解決這個問題,TCP 為每個連線設有一個持續定時器,只要 TCP 連線一方收到對方的零視窗通知,就啟動持續計時器。
如果持續計時器超時,就會傳送視窗探測 ( Window probe ) 報文,而對方在確認這個探測報文時,給出自己現在的接收視窗大小。
擁塞控制
在網路出現擁堵時,如果繼續傳送大量資料包,可能會導致資料包時延、丟失等,這時 TCP 就會重傳資料,但是一重傳就會導致網路的負擔更重,於是會導致更大的延遲以及更多的丟包
於是,就有了擁塞控制,控制的目的就是避免「傳送方」的資料填滿整個網路。又引入了擁塞視窗的概念
擁塞視窗 cwnd是傳送方維護的一個的狀態變數,它會根據網路的擁塞程度動態變化的。
- 只要網路中沒有出現擁塞,
cwnd
就會增大; - 但網路中出現了擁塞,
cwnd
就減少;
那麼怎麼知道當前網路是否出現了擁塞呢?
其實只要「傳送方」沒有在規定時間內接收到 ACK 應答報文,也就是發生了超時重傳,就會認為網路出現了擁塞。
慢啟動
慢啟動的演算法記住一個規則就行,當傳送方每收到一個 ACK,擁塞視窗 cwnd 的大小就會加 1。
增加到什麼時候:
慢啟動門限 ssthresh
(slow start threshold)狀態變數。
- 當
cwnd
<ssthresh
時,使用慢啟動演算法。 - 當
cwnd
>=ssthresh
時,就會使用「擁塞避免演算法」
擁塞避免
進入擁塞避免演算法後,它的規則是:每當收到一個 ACK 時,cwnd 增加 1/cwnd。
也就是說擁塞視窗的增長速度從指數增長變成線性增長,就這麼一直增長著後,網路就會慢慢進入了擁塞的狀況了,於是就會出現丟包現象,這時就需要對丟失的資料包進行重傳。
當觸發了重傳機制,也就進入了「擁塞發生演算法」。
擁塞發生演算法
發生超時重傳的擁塞發生演算法:
這個時候,ssthresh 和 cwnd 的值會發生變化:
ssthresh
設為cwnd/2
,cwnd
重置為1
(是恢復為 cwnd 初始化值,我這裡假定 cwnd 初始化值 1)
接著,就重新開始慢啟動,這種方式太激進了,反應也很強烈,會造成網路卡頓。
發生快速重傳的擁塞發生演算法:
cwnd = cwnd/2
,也就是設定為原來的一半;ssthresh = cwnd
;- 進入快速恢復演算法
快速恢復
快速重傳和快速恢復演算法一般同時使用
進入快速恢復演算法如下:
- 擁塞視窗
cwnd = ssthresh + 3
( 3 的意思是確認有 3 個資料包被收到了); - 重傳丟失的資料包;
- 如果再收到重複的 ACK,那麼 cwnd 增加 1;
- 如果收到新資料的 ACK 後,把 cwnd 設定為第一步中的 ssthresh 的值,如果收到新資料的 ACK 後,把 cwnd 設定為第一步中的 ssthresh 的值,可以回到恢復之前的狀態了,也即再次進入擁塞避免狀態;