引言:TCP數(shù)據(jù)發(fā)送
如果發(fā)送一個(gè)新的包,需要等待上一個(gè)包的 ACK 回來之后才能發(fā)送,這樣效率顯然是很低的。也就是每經(jīng)過一個(gè) RTT(RTT指的是一個(gè)數(shù)據(jù)包從發(fā)送到接收到ACK確認(rèn)包的花費(fèi)時(shí)間,RTT是會隨著網(wǎng)絡(luò)的變化而變化) 的時(shí)間,我們只能發(fā)送一個(gè)包,假設(shè)一個(gè) RTT 是 100ms,那在一秒中我們甚至只能發(fā)送 10 個(gè)包,這完全是不可接受的。
其實(shí)我們在等待 ACK 的時(shí)候沒有必要停止后續(xù)包的發(fā)送,因?yàn)榫W(wǎng)絡(luò)傳輸雖然不穩(wěn)定,但大部分包往往還是可達(dá)的,這樣我們就可以獲得數(shù)倍的傳輸效率提升。如果真的不幸遇到了丟包,接收端 ACK 姍姍來遲的時(shí)候,也就告訴了我們某個(gè)序列號之前的所有包全部收到,我們再根據(jù)一定的策略,嘗試重新發(fā)送對應(yīng)丟失的包就可以了。
所以自然而然的,發(fā)送方需要緩存已發(fā)出但尚未收到 ACK 的包,接收方收到包但沒有被用戶進(jìn)程消費(fèi)之前也得把收到的包留著。但是,緩存是有大小限制的,程序消費(fèi)數(shù)據(jù)和鏈路傳輸數(shù)據(jù)的能力也是有限的,發(fā)送端和接受端都需要一種機(jī)制來限制可發(fā)送或者可接收數(shù)據(jù)的最大范圍。
于是滑動(dòng)窗口和擁塞窗口應(yīng)運(yùn)而生。
這兩個(gè)算法核心都是為了防止網(wǎng)絡(luò)中發(fā)送的包太多。但兩者的目的不同,滑動(dòng)窗口機(jī)制,可以用來控制流量,防止接收方處理不過來消息;同樣基于窗口機(jī)制的擁塞控制算法,則用來處理網(wǎng)絡(luò)上數(shù)據(jù)包太多的情況,以避免網(wǎng)絡(luò)中出現(xiàn)擁塞。
滑動(dòng)窗口概述
滑動(dòng)窗口實(shí)現(xiàn)了TCP流控制。首先明確滑動(dòng)窗口的范疇:TCP是雙工的協(xié)議,會話的雙方都可以同時(shí)接收和發(fā)送數(shù)據(jù)。TCP會話的雙方都各自維護(hù)一個(gè)發(fā)送窗口和一個(gè)接收窗口。各自的接收窗口大小取決于應(yīng)用、系統(tǒng)、硬件的限制(TCP傳輸速率不能大于應(yīng)用的數(shù)據(jù)處理速率)。各自的發(fā)送窗口則要求取決于對端通告的接收窗口。
滑動(dòng)窗口解決的是流量控制的的問題,就是如果接收端和發(fā)送端對數(shù)據(jù)包的處理速度不同,如何讓雙方達(dá)成一致。接收端的緩存?zhèn)鬏敂?shù)據(jù)給應(yīng)用層,但這個(gè)過程不一定是即時(shí)的,如果發(fā)送速度太快,會出現(xiàn)接收端數(shù)據(jù)overflow,流量控制解決的是這個(gè)問題。
接收端會建立一個(gè)滑動(dòng)窗口,由接收方向發(fā)送方通告,TCP 首部里的 window 字段就是用來表示窗口大小的,窗口表示的就是接收方目前能接收的緩沖區(qū)的剩余大小。
但是發(fā)送方也會根據(jù)這個(gè)通告窗口的大小建立自己的滑動(dòng)窗口。為了兼顧效率和可靠性,在發(fā)送方,所有未收到 ACK 的消息雖然可以發(fā)送,但是在收到 ACK 之前是一定要在緩沖區(qū)中保存的。
發(fā)送方滑動(dòng)窗口
發(fā)送方的發(fā)送緩存內(nèi)的數(shù)據(jù)都可以被分為4類:
- 已發(fā)送,已收到ACK
- 已發(fā)送,未收到ACK
- 未發(fā)送,但允許發(fā)送
- 未發(fā)送,但不允許發(fā)送
其中類型2和3都屬于發(fā)送窗口。
- 第一部分就是已經(jīng)發(fā)送且收到 ACK 的部分,已經(jīng)成功發(fā)送,所以不需要在緩沖區(qū)保留了。
- 第二部分是已發(fā)送但尚未收到 ACK 的部分,該部分需要保留在緩沖區(qū)。
- 第三部分是還沒有發(fā)送,但是還在接收方通告窗口也就是處理范圍內(nèi)的數(shù)據(jù),這塊我們也可以稱為可用窗口;第二、第三部分一起構(gòu)成了整個(gè)發(fā)送窗口。
- 最后一部分則是需要發(fā)送,但已經(jīng)超過接收方通告窗口范圍的部分,這一部分在沒有收到新的 ACK 之前,發(fā)送方是不會發(fā)送這些數(shù)據(jù)的。通過這個(gè)限制,發(fā)送的數(shù)據(jù)就一定不會超過接收方的緩沖區(qū)了。
零窗口
但如果發(fā)送方一直沒有收到 ACK,隨著數(shù)據(jù)不斷被發(fā)送,很快可用窗口就會被耗盡。在這種情況下,發(fā)送方也就不會繼續(xù)發(fā)送數(shù)據(jù)了,這種發(fā)送端可用窗口為零的情況稱為零窗口。
正常來說,等接收端處理了一部分?jǐn)?shù)據(jù),又有了新的可用窗口之后,就會再次發(fā)送 ACK 報(bào)文通告發(fā)送端自己有新的可用窗口(因?yàn)榘l(fā)送端的可用窗口是受接收端控制的)。
但是,萬一要是 ACK 消息在網(wǎng)絡(luò)傳輸中正好丟包了,那發(fā)送端還能感知到接收端窗口的變化嗎?其實(shí)是不會的,在這個(gè)情況下,接收端就會一直等著發(fā)送端發(fā)送數(shù)據(jù),而發(fā)送端也還會以為接收端仍然處于零窗口的狀態(tài),這樣一直互相等待,就好像進(jìn)入了死鎖狀態(tài)。
解決辦法也很簡單,我們可以再引入一個(gè)零窗口定時(shí)器,如果發(fā)送端陷入零窗口的狀態(tài),就會啟動(dòng)這個(gè)定時(shí)器,去定時(shí)地詢問接收端窗口是否可用了,這也是在分布式系統(tǒng)中常見的處理丟包的方式之一。
接收方滑動(dòng)窗口
接收方的緩存數(shù)據(jù)分為3類:
- 已接收
- 未接收但準(zhǔn)備接收
- 未接收而且不準(zhǔn)備接收
其中類型2屬于接收窗口。
- 已經(jīng)接收并確認(rèn)的數(shù)據(jù);
- 未收到但可以接收的數(shù)據(jù),這一部分也就是接收窗口;
- 剩下的就是緩沖區(qū)放不下的區(qū)域,也就是不可接收的區(qū)域。
如果進(jìn)程讀取緩沖區(qū)速度有所變化,接收端可能也會改變接收窗口的大小,每次通告給發(fā)送端,就可以控制發(fā)送端的發(fā)送速度了。這就是所謂的滑動(dòng)窗口,也就是流量控制機(jī)制。
擁塞窗口
在實(shí)際網(wǎng)絡(luò)中,因?yàn)榇罅康陌鼈鬏?,可能?dǎo)致中間某些節(jié)點(diǎn)的緩沖區(qū)滿載,從而多余的包被丟棄,需要重新發(fā)送,情況越發(fā)惡化,最差的時(shí)候,網(wǎng)絡(luò)上的包都是重傳的包并且反復(fù)地丟棄;整個(gè)網(wǎng)絡(luò)傳輸能力甚至可以降低為 0。
網(wǎng)絡(luò)中每個(gè)節(jié)點(diǎn)不會有全局的網(wǎng)絡(luò)通信情況,唯一能發(fā)現(xiàn)的就是自己的部分包丟了,這種時(shí)候它就有理由懷疑網(wǎng)絡(luò)環(huán)境劣化,可能產(chǎn)生了擁塞。
TCP 是一個(gè)比較無私的協(xié)議,在這種情況下,會選擇減少自己發(fā)送的包。當(dāng)網(wǎng)絡(luò)上大部分通信協(xié)議傳輸層都采用的是 TCP 協(xié)議時(shí),在出現(xiàn)擁塞的情況下,大部分節(jié)點(diǎn)都會不約而同地減少自己傳輸?shù)陌?,這樣網(wǎng)絡(luò)擁塞情況就會得到極大的緩解,一直處于比較好的網(wǎng)絡(luò)狀態(tài)。
所以我們就需要在發(fā)送端定義一個(gè)窗口CWND(congestion window),也就是擁塞窗口;
引入擁塞控制機(jī)制的 TCP 協(xié)議,發(fā)送端最大的發(fā)送范圍是擁塞窗口和滑動(dòng)窗口中較小的一個(gè),即 Min [ rwnd, cwnd ]。擁塞窗口會動(dòng)態(tài)地隨著網(wǎng)絡(luò)情況的變化而進(jìn)行調(diào)整,大體上的策略是如果沒有出現(xiàn)擁塞,我們擴(kuò)大窗口大小,否則就減少窗口大小。
具體是如何實(shí)現(xiàn)的呢?經(jīng)典擁塞控制算法主要包括四個(gè)部分:
- 慢啟動(dòng)/慢開始(slow-start)
- 擁塞避免
- 擁塞發(fā)生
- 快速恢復(fù)
1.慢啟動(dòng)/慢開始(slow-start)
在不確定擁塞是否會發(fā)生的時(shí)候,我們不會一上來就發(fā)送大量的包,而是會采用指數(shù)倍增窗口的大小,窗口大小從 1 開始嘗試,然后嘗試 2、4、8、16 等越來越大的窗口。
2.擁塞避免
這樣,倍增的方式窗口就會很快擴(kuò)大;我們會在窗口大到一定程度后(達(dá)到慢啟動(dòng)門限ssthresh),減慢增加的速度,轉(zhuǎn)成線性擴(kuò)大窗口的方式,也就是每次收到新的 ACK 沒有丟包的話只比上次窗口增大 1。整個(gè)過程看起來就像這樣:
3.擁塞發(fā)生
隨著窗口進(jìn)一步緩慢增加,終于有一天,網(wǎng)絡(luò)還是遇到了丟包的情況,我們就會假定這是擁塞造成的。這個(gè)時(shí)候會進(jìn)行超時(shí)重傳或者快速重傳。
4.快速恢復(fù)
- 超時(shí)重傳:往往擁塞情況嚴(yán)重,采取策略也會更激進(jìn)一些,會直接將 ssthresh 設(shè)置為重傳發(fā)生時(shí)窗口大小的一半,而窗口大小直接重置為 0,再進(jìn)入慢啟動(dòng)階段。

- 快速重傳:如果我們連續(xù) 3 次收到同樣序號的 ACK,包還能回傳,說明這個(gè)時(shí)候可能只是碰到了部分丟包,網(wǎng)絡(luò)阻塞還沒有很嚴(yán)重,此時(shí)就會采用柔和一點(diǎn)的策略,也就是快速重傳策略。我們會先把擁塞窗口變成原來的一半,ssthresh 也就設(shè)置成當(dāng)前的窗口大小,然后開始執(zhí)行擁塞避免算法。有些實(shí)現(xiàn)也會把擁塞窗口直接設(shè)置為 ssthresh+3,本質(zhì)上區(qū)別不大。

總結(jié)
總體而言,TCP 就是通過滑動(dòng)窗口、擁塞窗口這兩個(gè)簡單的窗口實(shí)現(xiàn)了流量控制和擁塞控制。
滑動(dòng)窗口由接收端控制,向發(fā)送端通告,這樣就可以保證發(fā)送端發(fā)出的包數(shù)量上限是明確的,也就不會存在淹沒接收端導(dǎo)致來不及處理的情況。文章來源:http://www.zghlxwxcb.cn/news/detail-445590.html
擁塞窗口由發(fā)送端控制,它會根據(jù)網(wǎng)絡(luò)中的情況動(dòng)態(tài)的調(diào)整,通過慢啟動(dòng)、擁塞避免、擁塞發(fā)生、快速恢復(fù)四個(gè)算法,就可以很好地調(diào)整窗口的大小。和滑動(dòng)窗口一起限制了發(fā)送端最大的發(fā)送范圍,從而保證了擁塞在網(wǎng)絡(luò)上不會發(fā)生。文章來源地址http://www.zghlxwxcb.cn/news/detail-445590.html
到了這里,關(guān)于TCP協(xié)議之滑動(dòng)窗口和擁塞窗口的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!