上一篇文章一起了解了一下 TCP 基礎(chǔ)知識:TCP 基礎(chǔ)(三次握手/四次揮手)
- TCP 的特點
- TCP 協(xié)議描述
- TCP 三次握手
- TCP 四次揮手
這一篇文章主要是一起來深入了解學(xué)習(xí)一下關(guān)于 TCP 的其他的點。
1. 滑動窗口
A、順序問題
B、丟包問題
C、流量控制
2. 擁塞窗口
TCP 可靠性的體現(xiàn)
首先我們知道 TCP 是一個可靠的連接,對應(yīng)具體的現(xiàn)象就是所有的 TCP 包正常通信過程中都是有一問一答的過程,如果異常情況,一定時間內(nèi)沒有收到這個回復(fù)的包,那么發(fā)送端就會重新發(fā)送,直到有回復(fù)。我們前面又說過序列號和確認(rèn)號的意思,在這里深入討論的時候,序列號和確認(rèn)號將是最重要的一點。
窗口
窗口是什么?
按照 TCP 可靠性來理解那么就會出現(xiàn),所有的包都必須要一個一個來,收到一個才能進(jìn)行下一個,這種方式確實可以解決基本的可靠性的問題(這種方式叫累計應(yīng)答),但是無疑會讓使得傳輸速率降低。所以就需要一個緩存區(qū)間,緩存已經(jīng)接到的數(shù)據(jù)。例如:一次性發(fā)送端發(fā)送 10 條數(shù)據(jù),由于各種原因,其中只是缺少了第 3 條數(shù)據(jù),其他的數(shù)據(jù)都已經(jīng)接收到了,最好的方式肯定是能夠只要發(fā)送重新發(fā)送一次第 3 條數(shù)據(jù)就好了。那么緩存其他 9 個數(shù)據(jù)的緩沖區(qū)就是窗口,而且窗口是有大小的,還記得我們在 TCP 報文格式中說的嗎?其中有一個字段就是窗口的大小。因為發(fā)送數(shù)據(jù)都是有回復(fù)的,所以在發(fā)送端也是有窗口的,所以窗口是在發(fā)送端和接受端都是有窗口的。
窗口在發(fā)送端區(qū)域:
- 發(fā)送了并且已經(jīng)確認(rèn)了的。
- 發(fā)送了尚未確認(rèn)的。
- 沒有發(fā)送,但是等待發(fā)送的。
- 沒有發(fā)送,并且暫時不會發(fā)送的。
數(shù)據(jù)格式如下:

其中:
LastByteAcked: 表示第一部分和第二部分的分界線。
LastByteSent: 表示第二部分和第三部分的分界線。
WillByteSend: 表示第三部分和第四部分的分界線。
第二部分和第三部分就是組成了 TCP 協(xié)議中定義的窗口大?。篈dvertised window .也就是我們在 TCP 協(xié)議中關(guān)于窗口的有效位的數(shù)據(jù)。
其實理論上看起來第三部分和第四部分是不需要分開,但是為了流量控制,總不能一股腦的全部傳給接收端。
窗口在接受端區(qū)域:
- 接受并且確認(rèn)過的。
- 還沒有接受但是馬上就能接受的。
- 還沒有接受而且是接受不了的部分。
數(shù)據(jù)格式如下:

其中:
MaxRcvBuffer:表示最大的緩存數(shù)據(jù)量。
LastByteRead :表示之前的數(shù)據(jù)都已經(jīng)上報給應(yīng)用層,從這個數(shù)據(jù)開始都是沒有上報應(yīng)用層的。
NextByteExpected - LastByteRead :表示已經(jīng)確認(rèn)接受了,但是上報給應(yīng)用層的數(shù)據(jù)。
其中 TCP 中的窗口位數(shù)據(jù)大小( AdveriseWindow )就是: MaxRecBuffer -( NextByteExpected - LastByteRead -1).
接受端的第二部分和第一部分有一個明顯區(qū)別,就是第一部分一定是連續(xù)的,而第二部分,可能由于其他原因,導(dǎo)致其中某些數(shù)據(jù)沒有及時到達(dá),存在缺失的情況。
順序問題和丟包問題:
根據(jù)上述的兩個數(shù)據(jù)結(jié)構(gòu)圖,我們模擬一種場景:
在發(fā)送端:
1.2.3 已經(jīng)發(fā)送確認(rèn)了;4~9 已經(jīng)發(fā)送未確認(rèn);10 ~ 12 未發(fā)送,等待發(fā)送。13 ~ 15 未發(fā)送暫不發(fā)送。
在接受端:
1 ~ 5 已經(jīng)接受并確認(rèn);
另外我們假設(shè) 6、7 還沒有接受的,8 ~ 14 都已經(jīng)接受到了,但是還沒有確認(rèn)。
兩端綜合來看:
其中 1.2.3 是接受端和發(fā)送端都已經(jīng)確認(rèn)好了的,沒有什么異議。此時 4、5 的 ACK 還沒有到達(dá)發(fā)送端。6、7 已經(jīng)發(fā)送了,但是有接受到,可能是包丟失了,8 ~ 14 已經(jīng)接收到了,因為 6、7 還沒有確認(rèn),所以他們也不能確認(rèn)。
根據(jù)上述場景我們來分析重傳機制:
4、5 的 ACK 沒有被發(fā)送端接收到,那么發(fā)送端會超時重傳,就是對那些,發(fā)出去沒有 ACK 的包重發(fā)( 沒有收到回復(fù)就認(rèn)為這個發(fā)出的數(shù)據(jù),接收端可能沒有接受到這個信息,也有可能接收端接收到了消息并且 ACK 也回了,發(fā)送端由于各種原因沒有接收到或者沒有及時接收到 ),什么時候開始重傳呢?重傳機制中這個時間( RTO )是必須大于往返時間 RTT ,否則引起沒有必要的重傳。也不能太長,因為等待 ACK 浪費時間,而影響訪問速度。TCP 會網(wǎng)絡(luò) RTT 進(jìn)行實時采樣,這個值會根據(jù)網(wǎng)絡(luò)狀態(tài)變化而變化,RTO 也會隨著者 RTT 變化而變化,我們稱這種變化為自適用重傳算法。
RTT 是什么?
RTT 是 Round Trip Time 的縮寫,表示的含義為發(fā)送一個數(shù)據(jù)包,到接受到對應(yīng)的 ACK,所花費的時間。主要由三部分組成 “鏈路的傳播時間( 來回路程 ) + 接受端系統(tǒng)的處理時間 + 路由器緩存中的排隊和處理時間”。其中路由器緩存排隊和處理時間會會根據(jù)網(wǎng)絡(luò)時間的變化的特別的明顯。其余的兩個相對會比較固定。
RTO是什么?
RTO 是 Retransmission TimeOut 即重傳超時時間
基于上面的場景繼續(xù)分析,如果此時的重發(fā) 4 的包,此時接收端收到 4 ,它發(fā)現(xiàn)自己已經(jīng)有了 4 ,所以會把 4 的包拋棄掉,但是回復(fù)一個 4 、5的 ACK,因為 4、5 都已經(jīng)接收到了,但接收端已經(jīng)接收到了 4 、5的ACK,1、2、3、4 、5那么全部發(fā)送接收確認(rèn)。TCP 會把接收的數(shù)據(jù)放在緩存區(qū),當(dāng)收到重發(fā)的時候,會對接收信息進(jìn)行“累計通知”,表示 5 我也收到
繼續(xù):
因為 6、7 也已經(jīng)發(fā)送了,但是接收端也沒有接收到,超過其 RTO 之后,那么就會重發(fā)6、7 。此時接收端接收到了 7 ,但是沒有接收到 6 ,此時會不會對 7 的包進(jìn)行回復(fù)的,因為這個時候已經(jīng)在數(shù)據(jù)產(chǎn)生了亂序,根據(jù) TCP 的協(xié)議的規(guī)定,當(dāng)接收端收到亂序片段時,需要重復(fù)發(fā)送 ACK 。
但是如果遇到一種場景的時間,需要能夠快速處理呢?比如說 8、9、10包都已經(jīng)接收到了,但是 7 已經(jīng)沒有接收到。那么這個時候有一種快速重傳機制,即如果我接收到了連續(xù)三個沉余的包(dup ACK ),發(fā)送發(fā)就認(rèn)為這個序號的包丟失,就可以立即重傳,如:8、9、10已經(jīng)接收到了,但是 7 還是沒有接收到,那么此時又接收到了 9 那么,此時還是針對 6 的包回復(fù),表示我希望下一個包希望接收的 7 那么此時,再連續(xù)發(fā)送三個沉余(dup ACK),接收端認(rèn)為 7 丟失,進(jìn)行快速重傳。
還有一種方式就是加上一個 SACK,通過這種方式,可以一下子看出來到底是誰丟了。
SACK 是一個 TCP 的選項,來允許 TCP 單獨發(fā)送確認(rèn)非連續(xù)的片段,可以用于告知真正丟失的包,只播丟失的包,但是需要兩端都支持這個才行。
流量控制問題
在 TCP 的數(shù)據(jù)格式中,是有一個字段表示窗口大小的。
在發(fā)送端和接收端都會有一個允許發(fā)送的窗口和允許接收的窗口。
前面有說過,窗口大小的定義。正常情況下,沒確認(rèn)以一個信息,窗口的起始序號,都會像左邊移動。大小也會跟隨著 TCP 數(shù)據(jù)格式包一起同步到兩端。讓對方都知彼此窗口大小。
不過流量控制主要是指控制發(fā)送端的速度,不讓發(fā)送端發(fā)送的速率太快,接收端沒有辦法接收處理,然后數(shù)據(jù)溢出。流量控制由滑動窗口機制實現(xiàn)
發(fā)送端窗口(SWND):
如果發(fā)送端把所有的數(shù)據(jù)發(fā)送出去了,那么發(fā)送端的窗口大小為 0,我們知道窗口大小是由發(fā)送未確認(rèn)和未發(fā)送可發(fā)送組成,但是此時把所有可發(fā)送的數(shù)據(jù)全部發(fā)送出去之后。所有的數(shù)據(jù)處于未確認(rèn)的狀態(tài),那么窗口再也不能存儲新的數(shù)據(jù)進(jìn)來。所有的數(shù)據(jù)都是處于已發(fā)送等待確認(rèn)的狀態(tài)。
接收端窗口(RWND):
接收端窗口就是等待確認(rèn)的數(shù)據(jù)序號組成,正常每確認(rèn)一個窗口需要向右滑動和一個訊號,但是如果上次應(yīng)用不處理已接收確認(rèn)的數(shù)據(jù),而MaxRecvBuffer 中的大小又是固定的,那么窗口的大小,一定會越來越來越小,最后直到為 0 ,讓對方停止發(fā)送。
為了不出現(xiàn)死鎖情況(發(fā)送端等待接收端告訴他,有足夠窗口大小能夠支持接收數(shù)據(jù)了,接收端以為發(fā)送端收到了自己讓他發(fā)送信息的指令,兩邊就處于一種死鎖狀態(tài)),定義了機制接收端為 0 的時候,發(fā)送方會定時發(fā)送窗口探測數(shù)據(jù)包,看看是否能夠調(diào)整窗口大小,但是如果對方處理比較慢的時候,又不是空出來一個窗口就去傳滿數(shù)據(jù),處理方式為 當(dāng)窗口太小的時候,不更新窗口,直到達(dá)到一定程度的大小的時候或者某一個定義好的數(shù)據(jù),才會更新窗口

圖中 1.2.3 包是被接收并確認(rèn)的,所以為窗口對應(yīng)的包的序號 +3。
擁塞控制問題
網(wǎng)絡(luò)擁塞和流量控制的區(qū)別:
流量控制是為了控制窗口大小,擔(dān)心處理不過來,防止數(shù)據(jù)溢出。
擁塞控制的問題是為了防止過多的數(shù)據(jù)注入網(wǎng)絡(luò)中,避免出現(xiàn)網(wǎng)絡(luò)負(fù)載過大。
慢開始算法
發(fā)送端有一個變量叫擁塞窗口(cwnd),擁塞窗口的大小取決于網(wǎng)絡(luò)的擁塞情況,并且是動態(tài)變化的,和 RTO 有點類似。發(fā)送方一般讓自己的發(fā)送窗口不大于擁塞窗口。
慢開始的算法的思路:
一條 TCP 連接開始,cwnd設(shè)置為一, 一開始發(fā)送的只發(fā)送一個,當(dāng)收到一個確認(rèn)之后,cwnd 就加 一,因此發(fā)送兩個。每收到一個確認(rèn)就加一,cwmd為四,就發(fā)送四個,確認(rèn)四個,cwnd 就變成了八,就發(fā)送八個,就是成指數(shù)型增長。也就是從一個很小的值開始,不斷開始指數(shù)增加
擁塞避免算法
有一個閾值為:ssthresh = 65535 個字節(jié)。當(dāng)超過這個字節(jié)的時候,速度就不能成指數(shù)增長了,需要慢下來。此時每收到一個確認(rèn)后,就變成了加 1/cwnd,全部收到回復(fù)就相當(dāng)于加一。即在原來的基礎(chǔ)上加一。如果之前是 10,那么就變成為 11.此時變成了線性變化了。但是不管是什么樣的增長,都是已經(jīng)在增長,那么就意味著這個數(shù)值只會越來越大。直到在某一個指時,出現(xiàn)了網(wǎng)絡(luò)擁塞。擁塞就會可能造成丟包,需要超時重傳。
快速恢復(fù)法
根據(jù)我們之前將的超時重傳的方法,如果啟動可快速重傳,如果此時能夠收到三個重復(fù)確認(rèn)包時,那么 TCP 認(rèn)為當(dāng)前的情況不嚴(yán)重,因為大部分的包是沒有丟的,執(zhí)行“乘法減小”,此時會把 ssthresh 將為原來的一般,并且把 ssthresh 設(shè)置給 cwnd,后面每收到一個數(shù)據(jù)回復(fù)就 cwnd 就加一,呈線性增加。
整個過程如圖所示:

這個圖是用別人畫的。和我沒有半毛錢關(guān)系