關(guān)于TCP的幾個(gè)問題

今天聊聊TCP,老規(guī)矩,為了更符合讀者的思考邏輯,文章依然由問題來組織:

  1. 在一個(gè)不可靠的網(wǎng)絡(luò)中,如何做到可靠的傳輸?
  2. TCP的連接到底是啥?
  3. “三次握手”做了什么?
  4. “四次揮手”做了什么?
  5. 丟包重傳是怎么做的?
  6. 服務(wù)器處理不過來了, 你能發(fā)慢點(diǎn)嗎?
  7. 好慢啊,網(wǎng)絡(luò)卡了?

先補(bǔ)充一點(diǎn)前置知識(shí),我們討論的TCP,屬于TCP/IP模型的傳輸層(第四層),向下基于IP層,向上支撐了應(yīng)用層。

就像本文的結(jié)構(gòu)一樣,這個(gè)世界是由問題組成的,協(xié)議的誕生是為了解決問題。TCP解決了這樣一個(gè)問題:

問題1: 在一個(gè)不可靠的網(wǎng)絡(luò)中,如何做到可靠的傳輸?

這里說的可靠,并不是說發(fā)送的數(shù)據(jù)一定能收到,下層的IP包該丟還是丟;僅僅是指對(duì)方收到了我的包,會(huì)發(fā)一個(gè)響應(yīng)包給我,告訴我收到了;只要是沒收到響應(yīng)包,都按丟了處理,檢測(cè)到丟包后按照一定的邏輯進(jìn)行重發(fā),如果實(shí)在是收不到,就按照失敗來處理了。

關(guān)于可靠傳輸還有一個(gè)問題就是:亂序,TCP的包有嚴(yán)格的順序,如果后面的包先到了,接收端要能夠檢測(cè)到,并且正確的處理。亂序的原因可能是前面的包丟了,或者后面的包先到了。

為了便于理解,我們來看一下TCP Header的格式:


TCP Header格式

這個(gè)圖會(huì)多次出現(xiàn),這里我們只關(guān)注兩個(gè)數(shù)據(jù):

  • Sequence Number: 包的序號(hào),用于解決亂序的問題。
  • Acknowledgment Number: ACK,就是響應(yīng)包,用于解決丟包的問題。

所以,做到可靠的底層邏輯是:增加冗余。
與UDP相比,TCP的(幾乎)每個(gè)包都有響應(yīng)包,這已經(jīng)讓包的數(shù)量增加了一倍。另外,丟包時(shí)要重發(fā),甚至多次重發(fā),做到可靠的方式就是有組織地增加冗余。


TCP宣稱自己是面向連接的,傳輸數(shù)據(jù)之前要先建立連接,那么問題來了:

問題2: TCP的連接到底是啥?

按照老美的套路,我們先聊一聊它不是什么?

有一種相當(dāng)普遍的錯(cuò)誤理解

TCP連接是互聯(lián)網(wǎng)上的一條專屬通道,就像是在兩端建立了一座橋。

上面的錯(cuò)誤說法非常流行,建立TCP的連接對(duì)于IP層沒有任何改造。IP層也不關(guān)心這個(gè)包是是不是TCP的包。

TCP建立連接的含義是: 兩端的設(shè)備創(chuàng)建了一些數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)包含了對(duì)方的信息(IP和端口和狀態(tài)等),建立連接的過程就是數(shù)據(jù)結(jié)構(gòu)中連接狀態(tài)變?yōu)椤耙堰B接”的過程,后面發(fā)送端發(fā)送數(shù)據(jù)到接收端,接收端經(jīng)過檢查發(fā)現(xiàn)在自己數(shù)據(jù)結(jié)構(gòu)中包含了對(duì)方的IP和端口,就會(huì)正常地接收這個(gè)包,僅此而已。


所以,TCP連接僅僅是兩端維護(hù)的“連接狀態(tài)”,都說建立連接的過程叫“三次握手”,那么問題來了:

問題3: “三次握手”做了什么?

既然TCP的連接僅僅是“狀態(tài)維護(hù)”,那么TCP就有一套狀態(tài)集合,包含了TCP的所有狀態(tài),這套狀態(tài)集合就是TCP的狀態(tài)機(jī)。

回到TCP Header格式圖:

TCP Header格式

這次關(guān)注的數(shù)據(jù)是:

  • TCP Flags: 包的標(biāo)識(shí),主要用于狀態(tài)機(jī)的維護(hù)

關(guān)于TCP Flags的取值,可以掃一眼下面這張圖,先注意一下SYNFIN這兩值。

TCP Flags of TCP Header

直接講TCP的狀態(tài)變換非常生硬,我們穿插在連接建立和斷開的過程中來講,這樣比較直觀。

從包的發(fā)送角度來看,建立連接過程就是三個(gè)包發(fā)送且到達(dá)的過程,如下圖:


TCP連接狀態(tài)圖

過程大概是這樣(其實(shí)圖已經(jīng)非常直觀了):

  • 準(zhǔn)備階段,B要先監(jiān)聽一個(gè)端口,通常服務(wù)器監(jiān)聽的固定端口,狀態(tài)機(jī)變化: CLOSED -> LISTEN
  • A發(fā)送(1)(一個(gè)TCP Segment),TCP Flags為SYN, 表示希望同步初始序列號(hào); seq(上面提到的Sequence Number)為x,表示來自A的包起始的序列號(hào)是x. 狀態(tài)機(jī)變化: CLOSED -> SYN_SEND
  • (收到(1)之后) B發(fā)送(2), TCP Flags 為SYN, 表示同步初始序列號(hào);seq=y, 表示來自B的包起始序列號(hào)是y; ACK(上面提到的Acknowledgment Number)為x+1, 表示收到了A發(fā)來的seq=x的包。 狀態(tài)機(jī)變化: LISTEN -> SYN_RCVD
  • (收到(2)之后) A發(fā)送(3), ACK=y+1, 表示收到B發(fā)來的seq=y的包, 狀態(tài)機(jī)變化: SYN_SEND -> ESTABLISHED, 此時(shí)A端連接建立,A可以主動(dòng)發(fā)送數(shù)據(jù)了。
  • (收到(3)之后) B端狀態(tài)機(jī)變化: SYN_RCVD -> ESTABLISHED, 此時(shí)B端建立連接,B可以主動(dòng)發(fā)送數(shù)據(jù)了。

看到這里,那個(gè)經(jīng)典的問題又來了:

為什么非要三次呢?兩次不行嗎?四次不行嗎?

先給答案: 兩次真不行,四次不劃算

從連接過程中可以看到,TCP三次握手主要解決確定了下面的問題:

  1. 確認(rèn)對(duì)方是可以連通的, 也愿意讓我連(我發(fā)送的包,收到了對(duì)方的ACK)
  2. 確認(rèn)對(duì)方已經(jīng)知道了我的初始序列號(hào)(ISN),后面我發(fā)送的數(shù)據(jù)包seq與這個(gè)值有關(guān)系的

如果改成兩次,就不能確認(rèn)A已經(jīng)知道了B的ISN。
如果改成四次,是可以知道對(duì)方收到了(3),但也不能因此讓網(wǎng)絡(luò)更加可靠,所以不劃算。

細(xì)心的同學(xué)可能意識(shí)到一個(gè)問題: A 與 B連接連接的時(shí)間點(diǎn)是不同的,一般情況下(3)丟了肯定是重發(fā)(2), 但是

如果A建立連接后立馬發(fā)數(shù)據(jù),此時(shí)(3)丟了,B端還沒有建立連接,那該怎么辦呢?

這位同學(xué)很刁鉆啊,A發(fā)的數(shù)據(jù)到了,說明A建立連接了,說明A收到(2)了, 此時(shí)A發(fā)的數(shù)據(jù)包就起到了(3)的確認(rèn)作用,B會(huì)立馬建立連接然后處理這些數(shù)據(jù)。


說完了建立連接,我們看看關(guān)閉連接的過程:

問題4: “四次揮手”做了什么?

既然建立了連接,就要能夠斷開連接,服務(wù)器里為對(duì)端設(shè)備維護(hù)的數(shù)據(jù)結(jié)構(gòu)也需要釋放。

連接的斷開比建立要復(fù)雜一些,正常情況下,連接的斷開都是主動(dòng)的,過程如下圖::


TCP-Disconnect-Single
  • 前置條件,兩端都處于ESTABLISHED狀態(tài)(建立已建立)
  • 一方發(fā)起斷開請(qǐng)求,這里A發(fā)起斷開: A發(fā)送(1), TCP Flags 為FIN(FINISH), seq=x, 表示請(qǐng)求斷開連接, 狀態(tài)機(jī)變化: ESTABLISHED -> FIN_WAIT_1, 之后A不再向B發(fā)送應(yīng)用層數(shù)據(jù)。
  • (收到(1)之后) B發(fā)送(2) ACK=x+1表示收到A發(fā)送的seq=x的包,狀態(tài)機(jī)變化: ESTABLISHED -> CLOSE_WAIT
  • (收到(2)之后) A的狀態(tài)機(jī)變化: FIN_WAIT_1 -> FIN_WAIT_2
  • (B應(yīng)用層數(shù)據(jù)傳輸完成后) B發(fā)送(3), TCP Flags 為FIN, seq=y, 表示請(qǐng)求斷開連接。狀態(tài)機(jī)變化: CLOSE_WAIT -> LAST_ACK, 同時(shí)B不再向A發(fā)送應(yīng)用層數(shù)據(jù)。
  • (收到(3)之后) A發(fā)送(4) ACK=y+1, 表示收到B發(fā)送的seq=y的包,狀態(tài)機(jī)變化: FIN_WAIT_2 -> TIME_WAIT, 等待2MSL(Maximum Segment Lifetime, TCP Segment的最大生存時(shí)間),之后狀態(tài)機(jī)變化: TIME_WAIT -> CLOSED, 此時(shí)A斷開連接。
  • (收到(4)之后) B的狀態(tài)機(jī)發(fā)生變化: LAST_ACK -> CLOSED, 此時(shí)B斷開連接。

看到這里,同樣的問題又來了:

為什么是四次揮手,為什么不是三次?

答案顯而易見: 三次不行。

因?yàn)锳無法確認(rèn),在自己請(qǐng)求關(guān)閉時(shí),B是否還有應(yīng)用層數(shù)據(jù)需要發(fā)送。所以需要B確認(rèn)沒有應(yīng)用層數(shù)據(jù)發(fā)送時(shí),再發(fā)起一個(gè)斷開請(qǐng)求(3),如果B發(fā)送(3)之后直接關(guān)閉,在(3)丟包的時(shí)候,A會(huì)以為B還有數(shù)據(jù)要發(fā),A永遠(yuǎn)處于FIN_WAIT_2狀態(tài)。當(dāng)然Linux會(huì)設(shè)一個(gè)超時(shí),處理這種異常,但是大部分的流程是正常的,不應(yīng)該按照異常來處理。

細(xì)心的同學(xué)可能發(fā)現(xiàn)一個(gè)問題:

A收到(3)之后,沒有馬上關(guān)閉,而是進(jìn)入了一個(gè)TIME_WAIT狀態(tài),等待了2MSL才關(guān)閉?為什么?

主要是B沒收到(4)時(shí),有足夠的時(shí)間再發(fā)一次。這里的2MSL,TCP協(xié)議定的是2分鐘,實(shí)際中一般使用30秒。

這時(shí)候,一個(gè)經(jīng)驗(yàn)豐富的同學(xué)占了起來,問:

你說的都是正常情況,生活中的異常情況太多了,比如拔網(wǎng)線,手機(jī)關(guān)機(jī),坐在車上切換了基站,這些情況根本不會(huì)按照正常的流程走的。

這位同學(xué)你說的對(duì), 我們上面討論的都是主動(dòng)斷開連接,事實(shí)上,被動(dòng)斷開連接的情況也會(huì)出現(xiàn),而且會(huì)出現(xiàn)在各個(gè)階段,這個(gè)時(shí)候我們一般能夠檢測(cè)到目標(biāo)(ip+端口)不可達(dá),不過這涉及到一個(gè)叫ICMP的協(xié)議(就是ping命令使用的協(xié)議),這里就不做太多介紹了。


實(shí)際上,斷開連接還有一種情況: 雙方同時(shí)斷開,這也是一種正常情況,如下圖:

TCP-Disconnect-Both

雙方同時(shí)斷開并不要求雙方在同一時(shí)間點(diǎn)發(fā)送斷開的請(qǐng)求,只要是對(duì)方的斷開請(qǐng)求還沒收到,這時(shí)發(fā)出斷開請(qǐng)求,都算是同時(shí)斷開。
我們看到同時(shí)斷開的場(chǎng)景雙方的狀態(tài)變化是一致的(不要求時(shí)間一致),我們只講一端的狀態(tài)變化:

  • A發(fā)出(1), TCP Flags為FIN, seq=j, 表示請(qǐng)求斷開連接。狀態(tài)機(jī)變化: ESTABLISHED -> FIN_WAIT_1, A不在向B發(fā)送應(yīng)用層數(shù)據(jù).
  • A收到(2), 表示B請(qǐng)求斷開連接, 未收到斷開請(qǐng)求ACK,先收到了對(duì)方的斷開請(qǐng)求。A端意識(shí)到此時(shí)是同時(shí)斷開場(chǎng)景,發(fā)送(2)', 表示收到了B的斷開請(qǐng)求,狀態(tài)機(jī)變化: FIN_WAIT_1 -> CLOSING
  • A收到(1)', 表示B收到了A的斷開連接請(qǐng)求,狀態(tài)機(jī)變化: CLOSING -> TIME_WAIT, 等待2MSL后狀態(tài)機(jī)變化: TIME_WAIT -> CLOSED,此時(shí)A斷開連接。

聊到這里,我們可以對(duì)TCP的狀態(tài)機(jī)做個(gè)總結(jié)了,我們可以對(duì)TCP的狀態(tài)機(jī)做個(gè)總結(jié)了。這是一張重要又復(fù)雜的圖,不過如果前面的內(nèi)容都讀懂了,你理解這張圖就就不成問題了。

The TCP Finite State Machine (FSM)

在終端輸入netstat命令,現(xiàn)在你可以明白最后一列是什么意思了:

netstat 命令截圖


說完了TCP連接和斷開,就要聊聊傳輸過程中的事兒了,最先想到的就是丟包重傳了:

問題5: 丟包重傳是怎么做的?

丟包重傳是TCP保證可靠的重要機(jī)制,在TCP協(xié)議的實(shí)現(xiàn)中,會(huì)綜合很多機(jī)制來做到這一點(diǎn):

超時(shí)重傳機(jī)制

發(fā)送端會(huì)動(dòng)態(tài)地給數(shù)據(jù)包設(shè)置超時(shí)時(shí)間,如果超過這個(gè)時(shí)間沒有收到ACK,就會(huì)重新發(fā)送數(shù)據(jù)包,當(dāng)然,重新發(fā)送后超時(shí)時(shí)間又會(huì)調(diào)整。
超時(shí)重傳機(jī)制有一個(gè)問題,如果遇到亂序(reordering),后面的包先到了,由于中間的包沒有到,超時(shí)重傳機(jī)制會(huì)認(rèn)為后面的包也沒到。這時(shí)候有兩種選擇:

  • 只重傳第一個(gè)超時(shí)的,死等ACK
  • 將所有的包重傳

兩種選擇都不夠好,第一種會(huì)導(dǎo)致傳輸慢,第二種是浪費(fèi)帶寬。

快速重傳機(jī)制

為了解決超時(shí)重傳的缺陷,TCP引入了快速重傳機(jī)制(Fast Retransmit),簡單講,如果出現(xiàn)了亂序,就連續(xù)ACK三次第一個(gè)丟失的包,發(fā)送單連續(xù)收到3個(gè)ACK,明白這個(gè)包丟了,馬上重傳,而不是等待超時(shí)。

快速重傳只是解決了不用等超時(shí)的問題,還是沒解決“后面的包要不要重傳”的問題。因?yàn)椴荒艽_認(rèn)后面到底是哪幾個(gè)包到了。

SACK機(jī)制

還有一種更好的機(jī)制叫SACK(Selective Acknowledgment), 就是在TCP Header中加入SACK,標(biāo)記亂序時(shí)后面收到的包,比如(ACK6, SACK8, SACK9),發(fā)送方就清楚第7個(gè)包丟了。


問題6: 服務(wù)器處理不過來了, 你能發(fā)慢點(diǎn)嗎?

我們前面討論的都是怎么連接,怎么斷開,丟包之后怎么重傳的問題,其實(shí)還有一個(gè)重要的問題,如何保證我的網(wǎng)絡(luò)處理程序達(dá)到一種狀態(tài):
在條件允許的情況下,以最快的速度發(fā)送和接受數(shù)據(jù)。

畢竟網(wǎng)絡(luò)的性能是影響用戶體驗(yàn)的重要因素,這里的“條件允許”指的是:

  • 接收端來得及處理
  • 不會(huì)造成網(wǎng)絡(luò)擁堵(這個(gè)在問題7中討論)

換句話說,這個(gè)問題就是如何做好數(shù)據(jù)發(fā)送量的控制, 讓TCP在接收方有能力處理時(shí),做到最大化的數(shù)據(jù)傳輸, 這個(gè)控制就是流量控制。

TCP引入的機(jī)制是滑動(dòng)窗口(Sliding Window),窗口的大小會(huì)根據(jù)運(yùn)行狀態(tài)變化,通過這個(gè)窗口的大小來決定當(dāng)前可以發(fā)送多少數(shù)據(jù),從而進(jìn)行流量的控制。

如下圖所示,對(duì)于發(fā)送端,黑框就是滑動(dòng)窗口:


Figure-AdvertisedWindow

有了滑動(dòng)窗口,發(fā)送端數(shù)據(jù)可以分為4部分:

  • (#1): 已發(fā)送且收到ACK
  • (#2): 已發(fā)送未收到ACK
  • (#3): 可發(fā)送還未發(fā)送
  • (#4): 不可以發(fā)送(大于窗口了,對(duì)方處理不過來)

窗口是看到了,那窗口的大小怎么改變呢?

回到這張熟悉的圖:


TCP Header格式

TCP Header中有一個(gè)字段叫Window, 也叫AdvertisedWindow(滑動(dòng)窗口), 接收方會(huì)在這個(gè)字段中傳入窗口的大小,也就是自己的可以處理數(shù)據(jù)的最大值。就是說伴隨著ACK包的接收,發(fā)送方的的窗口大小可能會(huì)不斷改變,下圖描述了接收端一步一步把發(fā)送端滑動(dòng)窗口變?yōu)?的過程。

AdvertisedWindow Change Process

如果細(xì)心的話,會(huì)發(fā)現(xiàn)有很多細(xì)節(jié)的問題,比如:

滑動(dòng)窗口變?yōu)?了,那是不是永遠(yuǎn)都不發(fā)包了?

當(dāng)然不是,滑動(dòng)窗口變?yōu)?之后,發(fā)送方還是會(huì)發(fā)幾次特殊的包,這個(gè)包的用途就是問問接收方窗口有沒有變化。

會(huì)不會(huì)有這樣的情況: #3部分(上圖Figure-AdvertisedWindow)為1個(gè)字節(jié),滑動(dòng)窗口不變,那是不是后面每個(gè)包都發(fā)一個(gè)字節(jié)?

這個(gè)問題叫"糊涂窗口綜合癥"(Silly Window Syndrome), 實(shí)際上IP Header + TCP Header 就有幾十Byte, 每次只發(fā)送數(shù)據(jù)很少的包會(huì)很浪費(fèi)帶寬,最終也會(huì)導(dǎo)致很差的性能。
協(xié)議實(shí)現(xiàn)的過程中,這種細(xì)節(jié)非常多,各種實(shí)現(xiàn)中,統(tǒng)一的思路都是等到可發(fā)送的數(shù)據(jù)足夠多時(shí)再進(jìn)行發(fā)送。

關(guān)于滑動(dòng)窗口說一句題外話,左耳朵說"不了解TCP的滑動(dòng)窗口等于不了解TCP協(xié)議",作為一個(gè)很好的機(jī)制,滑動(dòng)窗口作為一個(gè)很好的機(jī)制也被沿用到了QUIC(HTTP/3, based on UDP)中。


問題7: 好慢啊,網(wǎng)絡(luò)卡了?

剛剛已經(jīng)提到了,影響網(wǎng)絡(luò)處理性能的因素中,除了兩端的處理速度,還有網(wǎng)絡(luò)中間的擁堵情況。

網(wǎng)絡(luò)擁堵時(shí),TCP能夠知道的就是丟包增加,這個(gè)時(shí)候如果一味地重傳會(huì)加重網(wǎng)絡(luò)的擁堵,惡性循環(huán)下去,可能導(dǎo)致大面積的網(wǎng)絡(luò)癱瘓?;谶@一點(diǎn)的考慮,TCP在處理網(wǎng)絡(luò)擁堵的時(shí)候,奉行了這樣的思路: 發(fā)現(xiàn)擁堵,先出讓自身的資源。考慮到TCP協(xié)議現(xiàn)在的流行程度,看得出TCP設(shè)計(jì)者的高瞻遠(yuǎn)矚。

應(yīng)對(duì)網(wǎng)絡(luò)擁堵的具體處理過程,就是TCP 的擁塞控制

說到擁塞控制,要先介紹一個(gè)擁塞窗口(Congestion Window, cwnd)的概念, 與滑動(dòng)窗口共同控制發(fā)送端的發(fā)送速度,具體的關(guān)系是已發(fā)送未接受數(shù)據(jù)量(上圖Figure-AdvertisedWindow中#2部分)不可以大于cwnd。

TCP的擁塞控制主要用了下面幾種策略:

1 慢啟動(dòng)(Slow Start)

慢啟動(dòng)的邏輯是:剛剛開始發(fā)送數(shù)據(jù)的連接,慢慢地提速到峰值,而不是一上來就拉到滑動(dòng)窗口的最大值。這樣能夠盡可能地避免新加入網(wǎng)絡(luò)的連接導(dǎo)致網(wǎng)絡(luò)擁堵。

慢啟動(dòng)處理過程是:

  • 連接建立時(shí),cwnd初始值為1(不同實(shí)現(xiàn)該值可能不同)
  • 收到一個(gè)ACK,cwnd+1 ( 一個(gè)包ACK后,可以發(fā)兩個(gè)包;兩個(gè)包都收到ACK后,就可以發(fā)四個(gè)包,指數(shù)增長)
  • 指數(shù)增長有上限,叫ssthresh(slow start threshold), 通常是65535Byte,cwnd超過ssthresh后,cwnd的變化會(huì)由"擁塞避免算法"處理。

我們看到,慢啟動(dòng)的過程只是為了不要一下子占滿了帶寬,如果網(wǎng)絡(luò)狀況好的話(快速ACK,一直不丟包),它的速度增長還是很快的,如果過程中遇到丟包,也會(huì)迅速降速(參考下面的3 擁塞狀態(tài)算法),也不會(huì)造成網(wǎng)絡(luò)的進(jìn)一步擁堵。

這里有一點(diǎn)需要注意,cwnd在實(shí)際工作中是以Byte為單位,剛剛的規(guī)則中把cwnd設(shè)為1,這里的1不是指1個(gè)字節(jié),為了理解的方便,這里指1個(gè)MSS(Maximum Segment Size 最大數(shù)據(jù)段尺寸, 數(shù)值上MSS = MTU(1500Byte)-IP Header-TCP Header),當(dāng)前cwnd的大小是1個(gè)MSS的字節(jié)數(shù),大約是1460Byte。

2 擁塞避免算法(Congestion Avoidance)

cwnd總不能永遠(yuǎn)指數(shù)增長,到了ssthresh后,就會(huì)降低增長速度,按照如下流程處理:

  • 收到一個(gè)ACK時(shí),cwnd = cwnd + 1/cwnd

這樣如果所有的數(shù)據(jù)都收到ACK,那么下一次請(qǐng)求書可以增加(1/cwnd*cwnd=)1,指數(shù)增長變成了線性增長,慢慢地增加到峰值。

3 擁塞狀態(tài)算法

TCP能夠檢測(cè)到擁堵的方式是基于丟包,正如問題5,丟包時(shí)通常有兩種情況:

超時(shí)重傳

TCP會(huì)認(rèn)為超時(shí)重傳的情況比較嚴(yán)重,處理的方式非常極端:

  • sshthresh = cwnd /2
  • cwnd = 1
  • 重新進(jìn)入慢啟動(dòng)過程

我們看到超時(shí)重傳的場(chǎng)景,TCP對(duì)于擁塞的處理非常激進(jìn),上限減半,窗口變?yōu)樽钚?,可謂"一超時(shí)回到解放前".

快速重傳(Fast Retransmit)

前面也說過了,在收到3個(gè)duplicate ACK時(shí)開啟了快速重傳,此時(shí)cwnd會(huì)有如下變化:

  • cwnd = cwnd /2
  • sshthresh = cwnd
  • 進(jìn)入“快速恢復(fù)算法”

不同場(chǎng)景,不同邏輯; 還沒到超時(shí)就抽到了3個(gè)duplicate ACK,看來網(wǎng)絡(luò)情況也沒有那么糟糕,那就不用回到解放前了,降為一半就行了,關(guān)于快速恢復(fù),見下文。

4 快速恢復(fù)算法(Fast Recovery)

快速恢復(fù)算法配合快速重傳算法,基于這樣的認(rèn)知: 既然3個(gè)duplicate ACK都收到了,看來網(wǎng)絡(luò)情況也沒有那么糟糕,速度都已經(jīng)減半了,可以以適當(dāng)快的速度進(jìn)行速度的恢復(fù)。
具體的算法如下:

  • cwnd = sshthresh + 3 * MSS
  • 重傳Duplicated ACKs指定的數(shù)據(jù)包
  • 如果再收到 duplicated Acks,那么cwnd = cwnd +1(一個(gè)ack,窗口+1)
  • 如果收到了新的Ack,那么,cwnd = sshthresh ,然后進(jìn)入擁塞避免的算法(一個(gè)ack,窗口加1/cwnd)

從這算法的過程我們發(fā)現(xiàn),還是沒有解決快速重傳算法的問題:不知道丟了一個(gè)包還是多個(gè)包。于是按照只要收到一個(gè)Duplicate Acks就折半(cwnd減半,sshthresh=cwnd),這就導(dǎo)致多個(gè)包Duplicate Acks會(huì)導(dǎo)致cwnd指數(shù)級(jí)下降。理論上,已經(jīng)折半了,就應(yīng)該把這個(gè)區(qū)間內(nèi)丟的包全部快速重傳,基于這樣的邏輯,后面快速恢復(fù)算法也進(jìn)行了變更-TCP New Reno.

另外,對(duì)于支持SACK的連接,還可以使用FACK(Forward Acknowledgment)算法。


我們看到,TCP對(duì)于擁塞控制的核心依據(jù)是丟包,發(fā)現(xiàn)Duplicate Acks就折半,發(fā)現(xiàn)丟包就直接“回到解放前”,TCP這樣設(shè)計(jì)的核心是基于當(dāng)時(shí)的場(chǎng)景,認(rèn)為丟包的原因就是網(wǎng)絡(luò)擁堵。

這樣的邏輯,有問題嗎?

Reference

https://nmap.org/book/tcpip-ref.html
http://www.serverframework.com/asynchronousevents/2011/01/time-wait-and-its-design-implications-for-protocols-and-scalable-servers.html
https://coolshell.cn/articles/11564.html
https://coolshell.cn/articles/11609.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容