每個時代空繁,都不會虧待會學習的人功炮。
在進入今天主題之前我先拋幾個問題娶聘,這篇文章一共提出 23 個問題。
TCP 握手一定是三次傍菇?TCP 揮手一定是四次?
為什么要有快速重傳界赔,超時重傳不夠用丢习?為什么要有 SACK牵触,為什么要有 D-SACK?
都知道有滑動窗口咐低,那由于接收方的太忙了滑動窗口降為了 0 怎么辦揽思?發(fā)送方就永遠等著了?
Silly Window 又是什么见擦?
為什么有滑動窗口流控還需要擁塞控制钉汗?
快速重傳一定要依賴三次重復 ACK ?
這篇文章我想由淺到深地過一遍 TCP鲤屡,不是生硬的搬出各個知識點损痰,從問題入手,然后從發(fā)展酒来、演進的角度來看 TCP卢未。
起初我在學計算機網(wǎng)絡(luò)的時候就有非常非常多的疑問,腦子里簡直充滿了十萬個為什么堰汉,而網(wǎng)絡(luò)又非常的復雜尝丐,發(fā)展了這么多年東西真的太多了,今天我就大致的淺顯地說一說我對 TCP 這些要點的理解衡奥。
好了爹袁,廢話不多說,開始上正菜矮固。
TCP 是用來解決什么問題失息?
TCP 即 Transmission Control Protocol,可以看到是一個傳輸控制協(xié)議档址,重點就在這個控制盹兢。
控制什么?
控制可靠守伸、按序地傳輸以及端與端之間的流量控制绎秒。夠了么?還不夠尼摹,它需要更加智能见芹,因此還需要加個擁塞控制,需要為整體網(wǎng)絡(luò)的情況考慮蠢涝。
這就是出行你我他玄呛,安全靠大家。
為什么要 TCP和二,IP 層實現(xiàn)控制不行么徘铝?
我們知道網(wǎng)絡(luò)是分層實現(xiàn)的,網(wǎng)絡(luò)協(xié)議的設(shè)計就是為了通信,從鏈路層到 IP 層其實就已經(jīng)可以完成通信了惕它。
你看鏈路層不可或缺畢竟咱們電腦都是通過鏈路相互連接的怕午,然后 IP 充當了地址的功能,所以通過 IP 咱們找到了對方就可以進行通信了淹魄。
那加個 TCP 層干啥郁惜?IP 層實現(xiàn)控制不就完事了嘛?
之所以要提取出一個 TCP 層來實現(xiàn)控制是因為 IP 層涉及到的設(shè)備更多揭北,一條數(shù)據(jù)在網(wǎng)絡(luò)上傳輸需要經(jīng)過很多設(shè)備,而設(shè)備之間需要靠 IP 來尋址吏颖。
假設(shè) IP 層實現(xiàn)了控制搔体,那是不是涉及到的設(shè)備都需要關(guān)心很多事情?整體傳輸?shù)男适遣皇谴蟠蛘劭哿耍?/p>
我舉個例子半醉,假如 A 要傳輸給 F 一個積木疚俱,但是無法直接傳輸?shù)剑枰?jīng)過 B缩多、C呆奕、D、E 這幾個中轉(zhuǎn)站之手衬吆。
這里有兩種情況:
- 假設(shè) BCDE 都需要關(guān)心這個積木搭錯了沒梁钾,都拆開包裹仔細的看看,沒問題了再裝回去逊抡,最終到了 F 的手中姆泻。
- 假設(shè) BCDE 都不關(guān)心積木的情況,來啥包裹只管轉(zhuǎn)發(fā)就完事了冒嫡,由最終的 F 自己來檢查這個積木答錯了沒拇勃。
你覺得哪種效率高?明顯是第二種孝凌,轉(zhuǎn)發(fā)的設(shè)備不需要關(guān)心這些事方咆,只管轉(zhuǎn)發(fā)就完事!
所以把控制的邏輯獨立出來成 TCP 層蟀架,讓真正的接收端來處理瓣赂,這樣網(wǎng)絡(luò)整體的傳輸效率就高了。
連接到底是什么片拍?
我們已經(jīng)知道了為什么需要獨立出 TCP 這一層钩述,并且這一層主要是用來干嘛的,接下來就來看看它到底是怎么干的穆碎。
我們都知道 TCP 是面向連接的牙勘,那這個連接到底是個什么東西?真的是拉了一條線讓端與端之間連起來了?
所謂的連接其實只是雙方都維護了一個狀態(tài)方面,通過每一次通信來維護狀態(tài)的變更放钦,使得看起來好像有一條線關(guān)聯(lián)了對方。
TCP 協(xié)議頭
在具體深入之前我們需要先來看看一些 TCP 頭的格式恭金,這很基礎(chǔ)也很重要操禀。
我就不一一解釋了,挑重點的說横腿。
首先可以看到 TCP 包只有端口颓屑,沒有 IP。
Seq 就是 Sequence Number 即序號耿焊,它是用來解決亂序問題的揪惦。
ACK 就是 Acknowledgement Numer 即確認號,它是用來解決丟包情況的罗侯,告訴發(fā)送方這個包我收到啦器腋。
標志位就是 TCP flags 用來標記這個包是什么類型的,用來控制 TPC 的狀態(tài)钩杰。
窗口就是滑動窗口纫塌,Sliding Window,用來流控讲弄。
三次握手
明確了協(xié)議頭的要點之后措左,我們再來看三次握手。
三次握手真是個老生常談的問題了避除,但是真的懂了么媳荒?不是浮在表面?能不能延伸出一些點別的驹饺?
我們先來看一下熟悉的流程钳枕。
首先為什么要握手,其實主要就是為了初始化Seq Numer赏壹,SYN 的全稱是 Synchronize Sequence Numbers鱼炒,這個序號是用來保證之后傳輸數(shù)據(jù)的順序性。
你要說是為了測試保證雙方發(fā)送接收功能都正常蝌借,我覺得也沒毛病昔瞧,不過我認為重點在于同步序號。
那為什么要三次菩佑,就拿我和你這兩個角色來說自晰,首先我告訴你我的初始化序號,你聽到了和我說你收到了稍坯。
然后你告訴我你的初始序號酬荞,然后我對你說我收到了搓劫。
這好像四次了?如果真的按一來一回就是四次混巧,但是中間一步可以合在一起枪向,就是你和我說你知道了我的初始序號的時候同時將你的初始序號告訴我。
因此四次握手就可以減到三次了咧党。
不過你沒有想過這么一種情形秘蛔,我和你同時開口,一起告訴對方各自的初始序號傍衡,然后分別回應(yīng)收到了深员,這不就是四次握手了?
我來畫個圖蛙埂,清晰一點倦畅。
看看是不是四次握手了? 不過具體還是得看實現(xiàn),有些實現(xiàn)可能不允許這種情況出現(xiàn)箱残,但是這不影響我們思考滔迈,因為握手的重點就是同步初始序列號止吁,這種情況也完成了同步的目標被辑。
初始序列號 ISN 的取值
不知道大家有沒有想過 ISN 的值要設(shè)成什么?代碼寫死從零開始敬惦?
想象一下如果寫死一個值盼理,比如 0 ,那么假設(shè)已經(jīng)建立好連接了俄删,client 也發(fā)了很多包比如已經(jīng)第 20 個包了宏怔,然后網(wǎng)絡(luò)斷了之后 client 重新,端口號還是之前那個畴椰,然后序列號又從 0 開始臊诊,此時服務(wù)端返回第 20 個包的ack,客戶端是不是傻了斜脂?
所以 RFC793 中認為 ISN 要和一個假的時鐘綁定在一起
ISN 每四微秒加一抓艳,當超過 2 的 32 次方之后又從 0 開始,要四個半小時左右發(fā)生 ISN 回繞帚戳。
所以 ISN 變成一個遞增值玷或,真實的實現(xiàn)還需要加一些隨機值在里面,防止被不法份子猜到 ISN片任。
SYN 超時了怎么處理偏友?
也就是 client 發(fā)送 SYN 至 server 然后就掛了,此時 server 發(fā)送 SYN+ACK 就一直得不到回復对供,怎么辦位他?
我腦海中一想到的就是重試,但是不能連續(xù)快速重試多次,你想一下棱诱,假設(shè) client 掉線了泼橘,你總得給它點時間恢復吧,所以呢需要慢慢重試迈勋,階梯性重試炬灭。
在 Linux 中就是默認重試 5 次,并且就是階梯性的重試靡菇,間隔就是1s重归、2s、4s厦凤、8s鼻吮、16s,再第五次發(fā)出之后還得等 32s 才能知道這次重試的結(jié)果较鼓,所以說總共等63s 才能斷開連接椎木。
SYN Flood 攻擊
你看到?jīng)] SYN 超時需要耗費服務(wù)端 63s 的時間斷開連接,也就說 63s 內(nèi)服務(wù)端需要保持這個資源博烂,所以不法分子就可以構(gòu)造出大量的 client 向 server 發(fā) SYN 但就是不回 server香椎。
使得 server 的 SYN 隊列耗盡,無法處理正常的建連請求禽篱。
所以怎么辦畜伐?
可以開啟 tcp_syncookies,那就用不到 SYN 隊列了躺率。
SYN 隊列滿了之后 TCP 根據(jù)自己的 ip玛界、端口、然后對方的 ip悼吱、端口慎框,對方 SYN 的序號,時間戳等一波操作生成一個特殊的序號(即 cookie)發(fā)回去后添,如果對方是正常的 client 會把這個序號發(fā)回來笨枯,然后 server 根據(jù)這個序號建連。
或者調(diào)整 tcp_synack_retries 減少重試的次數(shù)吕朵,設(shè)置 tcp_max_syn_backlog 增加 SYN 隊列數(shù)猎醇,設(shè)置 tcp_abort_on_overflow SYN 隊列滿了直接拒絕連接。
為什么要四次揮手努溃?
四次揮手和三次握手成雙成對硫嘶,同樣也是 TCP 中的一線明星,讓我們重溫一下熟悉的圖梧税。
為什么揮手需要四次沦疾?因為 TCP 是全雙工協(xié)議称近,也就是說雙方都要關(guān)閉,每一方都向?qū)Ψ桨l(fā)送 FIN 和回應(yīng) ACK哮塞。
就像我對你說我數(shù)據(jù)發(fā)完了刨秆,然后你回復好的你收到了。然后你對我說你數(shù)據(jù)發(fā)完了忆畅,然后我向你回復我收到了衡未。
所以看起來就是四次。
從圖中可以看到主動關(guān)閉方的狀態(tài)是 FIN_WAIT_1 到 FIN_WAIT_2 然后再到 TIME_WAIT家凯,而被動關(guān)閉方是 CLOSE_WAIT 到 LAST_ACK缓醋。
四次揮手狀態(tài)一定是這樣變遷的嗎
狀態(tài)一定是這樣變遷的嗎?讓我們再來看個圖绊诲。
可以看到雙方都主動發(fā)起斷開請求所以各自都是主動發(fā)起方送粱,狀態(tài)會從 FIN_WAIT_1 都進入到 CLOSING 這個過度狀態(tài)然后再到 TIME_WAIT。
揮手一定需要四次嗎掂之?
假設(shè) client 已經(jīng)沒有數(shù)據(jù)發(fā)送給 server 了抗俄,所以它發(fā)送 FIN 給 server 表明自己數(shù)據(jù)發(fā)完了,不再發(fā)了世舰,如果這時候 server 還是有數(shù)據(jù)要發(fā)送給 client 那么它就是先回復 ack 动雹,然后繼續(xù)發(fā)送數(shù)據(jù)。
等 server 數(shù)據(jù)發(fā)送完了之后再向 client 發(fā)送 FIN 表明它也發(fā)完了,然后等 client 的 ACK 這種情況下就會有四次揮手。
那么假設(shè) client 發(fā)送 FIN 給 server 的時候 server 也沒數(shù)據(jù)給 client图焰,那么 server 就可以將 ACK 和它的 FIN 一起發(fā)給client 僚稿,然后等待 client 的 ACK,這樣不就三次揮手了丐怯?
為什么要有 TIME_WAIT?
斷開連接發(fā)起方在接受到接受方的 FIN 并回復 ACK 之后并沒有直接進入 CLOSED 狀態(tài)喷好,而是進行了一波等待,等待時間為 2MSL读跷。
MSL 是 Maximum Segment Lifetime梗搅,即報文最長生存時間,RFC 793 定義的 MSL 時間是 2 分鐘效览,Linux 實際實現(xiàn)是 30s无切,那么 2MSL 是一分鐘。
那么為什么要等 2MSL 呢丐枉?
就是怕被動關(guān)閉方?jīng)]有收到最后的 ACK哆键,如果被動方由于網(wǎng)絡(luò)原因沒有到,那么它會再次發(fā)送 FIN瘦锹, 此時如果主動關(guān)閉方已經(jīng) CLOSED 那就傻了籍嘹,因此等一會兒闪盔。
假設(shè)立馬斷開連接,但是又重用了這個連接辱士,就是五元組完全一致泪掀,并且序號還在合適的范圍內(nèi),雖然概率很低但理論上也有可能颂碘,那么新的連接會被已關(guān)閉連接鏈路上的一些殘留數(shù)據(jù)干擾异赫,因此給予一定的時間來處理一些殘留數(shù)據(jù)。
等待 2MSL 會產(chǎn)生什么問題头岔?
如果服務(wù)器主動關(guān)閉大量的連接祝辣,那么會出現(xiàn)大量的資源占用,需要等到 2MSL 才會釋放資源切油。
如果是客戶端主動關(guān)閉大量的連接蝙斜,那么在 2MSL 里面那些端口都是被占用的,端口只有 65535 個澎胡,如果端口耗盡了就無法發(fā)起送的連接了孕荠,不過我覺得這個概率很低,這么多端口你這是要建立多少個連接攻谁?
如何解決 2MSL 產(chǎn)生的問題稚伍?
快速回收,即不等 2MSL 就回收戚宦, Linux 的參數(shù)是 tcp_tw_recycle个曙,還有 tcp_timestamps 不過默認是打開的。
其實上面我們已經(jīng)分析過為什么需要等 2MSL受楼,所以如果等待時間果斷就是出現(xiàn)上面說的那些問題垦搬。
所以不建議開啟,而且 Linux 4.12 版本后已經(jīng)咔擦了這個參數(shù)了艳汽。
前不久剛有位朋友在群里就提到了這玩意猴贰。
一問果然有 NAT 的身影。
現(xiàn)象就是請求端請求服務(wù)器的靜態(tài)資源偶爾會出現(xiàn) 20-60 秒左右才會有響應(yīng)的情況河狐,從抓包看請求端連續(xù)三個 SYN 都沒有回應(yīng)米绕。
比如你在學校,對外可能就一個公網(wǎng) IP馋艺,然后開啟了 tcp_tw_recycle(tcp_timestamps 也是打開的情況下)栅干,在 60 秒內(nèi)對于同源 IP 的連接請求中 timestamp 必須是遞增的,不然認為其是過期的數(shù)據(jù)包就會丟棄捐祠。
學校這么多機器碱鳞,你無法保證時間戳是一致的,因此就會出問題雏赦。
所以這玩意不推薦使用劫笙。
重用芙扎,即開啟 tcp_tw_reuse 當然也是需要 tcp_timestamps 的。
這里有個重點填大,tcp_tw_reuse 是用在連接發(fā)起方的戒洼,而我們的服務(wù)端基本上是連接被動接收方。
tcp_tw_reuse 是發(fā)起新連接的時候允华,可以復用超過 1s 的處于 TIME_WAIT 狀態(tài)的連接圈浇,所以它壓根沒有減少我們服務(wù)端的壓力。
它重用的是發(fā)起方處于 TIME_WAIT 的連接靴寂。
這里還有一個 SO_REUSEADDR 磷蜀,這玩意有人會和 tcp_tw_reuse 混為一談,首先 tcp_tw_reuse 是內(nèi)核選項而 SO_REUSEADDR 是用戶態(tài)選項百炬。
然后 SO_REUSEADDR 主要用在你啟動服務(wù)的時候褐隆,如果此時的端口被占用了并且這個連接處于 TIME_WAIT 狀態(tài),那么你可以重用這個端口剖踊,如果不是 TIME_WAIT庶弃,那就是給你個 Address already in use。
所以這兩個玩意好像都不行德澈,而且 tcp_tw_reuse 和tcp_tw_recycle歇攻,其實是違反 TCP 協(xié)議的,說好的等我到天荒地老梆造,你卻偷偷放了手缴守?
要么就是調(diào)小 MSL 的時間,不過也不太安全镇辉,要么調(diào)整 tcp_max_tw_buckets 控制 TIME_WAIT 的數(shù)量屡穗,不過默認值已經(jīng)很大了 180000,這玩意應(yīng)該是用來對抗 DDos 攻擊的摊聋。
所以我給出的建議是服務(wù)端不要主動關(guān)閉鸡捐,把主動關(guān)閉方放到客戶端栈暇。畢竟咱們服務(wù)器是一對很多很多服務(wù)麻裁,我們的資源比較寶貴。
自己攻擊自己
還有一個很騷的解決方案源祈,我自己瞎想的煎源,就是自己攻擊自己。
Socket 有一個選項叫 IP_TRANSPARENT 香缺,可以綁定一個非本地的地址手销,然后服務(wù)端把建連的 ip 和端口都記下來,比如寫入本地某個地方图张。
然后啟動一個服務(wù)锋拖,假如現(xiàn)在服務(wù)端資源很緊俏诈悍,那么你就定個時間,過了多久之后就將處于 TIME_WAIT 狀態(tài)的對方 ip 和端口告訴這個服務(wù)兽埃。
然后這個服務(wù)就利用 IP_TRANSPARENT 偽裝成之前的那個 client 向服務(wù)端發(fā)起一個請求侥钳,然后服務(wù)端收到會給真的 client 一個 ACK, 那 client 都關(guān)了已經(jīng)柄错,說你在搞啥子舷夺,于是回了一個 RST,然后服務(wù)端就中止了這個連接售貌。
超時重傳機制是為了解決什么問題给猾?
前面我們提到 TCP 要提供可靠的傳輸,那么網(wǎng)絡(luò)又是不穩(wěn)定的如果傳輸?shù)陌鼘Ψ經(jīng)]收到卻又得保證可靠那么就必須重傳颂跨。
TCP 的可靠性是靠確認號的敢伸,比如我發(fā)給你1、2恒削、3详拙、4這4個包,你告訴我你現(xiàn)在要 5 那說明前面四個包你都收到了蔓同,就是這么回事兒饶辙。
不過這里要注意,SeqNum 和 ACK 都是以字節(jié)數(shù)為單位的斑粱,也就是說假設(shè)你收到了1弃揽、2、4 但是 3 沒有收到你不能 ACK 5则北,如果你回了 5 那么發(fā)送方就以為你5之前的都收到了矿微。
所以只能回復確認最大連續(xù)收到包,也就是 3尚揣。
而發(fā)送方不清楚 3涌矢、4 這兩個包到底是還沒到呢還是已經(jīng)丟了,于是發(fā)送方需要等待快骗,這等待的時間就比較講究了娜庇。
如果太心急可能 ACK 已經(jīng)在路上了,你這重傳就是浪費資源了方篮,如果太散漫名秀,那么接收方急死了,這死鬼怎么還不發(fā)包來藕溅,我等的花兒都謝了匕得。
所以這個等待超時重傳的時間很關(guān)鍵,怎么搞巾表?聰明的小伙伴可能一下就想到了汁掠,你估摸著正常來回一趟時間是多少不就好了略吨,我就等這么長。
這就來回一趟的時間就叫 RTT考阱,即 Round Trip Time晋南,然后根據(jù)這個時間制定超時重傳的時間 RTO,即 Retransmission Timeout羔砾。
不過這里大概只好了 RTO 要參考下 RTT 负间,但是具體要怎么算?首先肯定是采樣姜凄,然后一波加權(quán)平均得到 RTO政溃。
RFC793 定義的公式如下:
1、先采樣 RTT
2态秧、SRTT = ( ALPHA * SRTT ) + ((1-ALPHA) * RTT)
3董虱、RTO = min[UBOUND,max[LBOUND,(BETA*SRTT)]]
ALPHA 是一個平滑因子取值在 0.8~0.9之間,UBOUND 就是超時時間上界-1分鐘申鱼,LBOUND 是下界-1秒鐘愤诱,BETA 是一個延遲方差因子,取值在 1.3~2.0捐友。
但是還有個問題淫半,RTT 采樣的時間用一開始發(fā)送數(shù)據(jù)的時間到收到 ACK 的時間作為樣本值還是重傳的時間到 ACK 的時間作為樣本值?
從圖中就可以看到匣砖,一個時間算長了科吭,一個時間算短了,這有點難猴鲫,因為你不知道這個 ACK 到底是回復誰的对人。
所以怎么辦?發(fā)生重傳的來回我不采樣不就好了拂共,我不知道這次 ACK 到底是回復誰的牺弄,我就不管他,我就采樣正常的來回宜狐。
這就是 Karn / Partridge 算法势告,不采樣重傳的RTT。
但是不采樣重傳會有問題肌厨,比如某一時刻網(wǎng)絡(luò)突然就是很差培慌,你要是不管重傳,那么還是按照正常的 RTT 來算 RTO柑爸, 那么超時的時間就過短了,于是在網(wǎng)絡(luò)很差的情況下還瘋狂重傳加重了網(wǎng)絡(luò)的負載盒音。
因此 Karn 算法就很粗暴的搞了個發(fā)生重傳我就將現(xiàn)在的 RTO 翻倍表鳍,哼馅而!就是這么簡單粗暴。
但是這種平均的計算很容易把一個突然間的大波動譬圣,平滑掉瓮恭,所以又搞了個算法,叫 Jacobson / Karels Algorithm厘熟。
它把最新的 RTT 和平滑過的 SRTT 做了波計算得到合適的 RTO屯蹦,公式我就不貼了,反正我不懂绳姨,不懂就不嗶嗶了登澜。
為什么還需要快速重傳機制?
超時重傳是按時間來驅(qū)動的飘庄,如果是網(wǎng)絡(luò)狀況真的不好的情況脑蠕,超時重傳沒問題,但是如果網(wǎng)絡(luò)狀況好的時候跪削,只是恰巧丟包了谴仙,那等這么長時間就沒必要。
于是又引入了數(shù)據(jù)驅(qū)動的重傳叫快速重傳碾盐,什么意思呢晃跺?就是發(fā)送方如果連續(xù)三次收到對方相同的確認號,那么馬上重傳數(shù)據(jù)毫玖。
因為連續(xù)收到三次相同 ACK 證明當前網(wǎng)絡(luò)狀況是 ok 的哼审,那么確認是丟包了,于是立馬重發(fā)孕豹,沒必要等這么久涩盾。
看起來好像挺完美的,但是你有沒有想過我發(fā)送1励背、2春霍、3、4這4個包叶眉,就 2 對方?jīng)]收到址儒,1、3衅疙、4都收到了莲趣,然后不管是超時重傳還是快速重傳反正對方就回 ACK 2。
這時候要重傳 2饱溢、3喧伞、4 呢還是就 2 呢?
SACK 的引入是為了解決什么問題?
SACK 即 Selective Acknowledgment潘鲫,它的引入就是為了解決發(fā)送方不知道該重傳哪些數(shù)據(jù)的問題翁逞。
我們來看一下下面的圖就知道了。
SACK 就是接收方會回傳它已經(jīng)接受到的數(shù)據(jù)溉仑,這樣發(fā)送方就知道哪一些數(shù)據(jù)對方已經(jīng)收到了挖函,所以就可以選擇性的發(fā)送丟失的數(shù)據(jù)。
如圖浊竟,通過 ACK 告知我接下來要 5500 開始的數(shù)據(jù)怨喘,并一直更新 SACK,6000-6500 我收到了振定,6000-7000的數(shù)據(jù)我收到了必怜,6000-7500的數(shù)據(jù)我收到了,發(fā)送方很明確的知道吩案,5500-5999 的那一波數(shù)據(jù)應(yīng)該是丟了棚赔,于是重傳。
而且如果數(shù)據(jù)是多段不連續(xù)的徘郭, SACK 也可以發(fā)送靠益,比如 SACK 0-500,1000-1500,2000-2500残揉。就表明這幾段已經(jīng)收到了胧后。
D-SACK 又是什么東西?
D-SACK 其實是 SACK 的擴展抱环,它利用 SACK 的第一段來描述重復接受的不連續(xù)的數(shù)據(jù)序號壳快,如果第一段描述的范圍被 ACK 覆蓋,說明重復了镇草,比如我都 ACK 到6000了你還給我回 SACK 5000-5500 呢眶痰?
說白了就是從第一段的反饋來和已經(jīng)接受到的 ACK 比一比,參數(shù)是 tcp_dsack梯啤,Linux 2.4 之后默認開啟竖伯。
那知道重復了有什么用呢?
1因宇、知道重復了說明對方收到剛才那個包了七婴,所以是回來的 ACK 包丟了。
2察滑、是不是包亂序的打厘,先發(fā)的包后到?
3贺辰、是不是自己太著急了户盯,RTO 太小了嵌施?
4、是不是被數(shù)據(jù)復制了先舷,搶先一步呢艰管?
滑動窗口干嘛用滓侍?
我們已經(jīng)知道了 TCP 有序號蒋川,并且還有重傳,但是這還不夠撩笆,因為我們不是愣頭青捺球,還需要根據(jù)情況來控制一下發(fā)送速率,因為網(wǎng)絡(luò)是復雜多變的夕冲,有時候就會阻塞住氮兵,而有時候又很通暢。
所以發(fā)送方需要知道接收方的情況歹鱼,好控制一下發(fā)送的速率泣栈,不至于蒙著頭一個勁兒的發(fā)然后接受方都接受不過來。
因此 TCP 就有個叫滑動窗口的東西來做流量控制弥姻,也就是接收方告訴發(fā)送方我還能接受多少數(shù)據(jù)南片,然后發(fā)送方就可以根據(jù)這個信息來進行數(shù)據(jù)的發(fā)送。
以下是發(fā)送方維護的窗口庭敦,就是黑色圈起來的疼进。
圖中的 #1 是已收到 ACK 的數(shù)據(jù)议泵,#2 是已經(jīng)發(fā)出去但是還沒收到 ACK 的數(shù)據(jù)划提,#3 就是在窗口內(nèi)可以發(fā)送但是還沒發(fā)送的數(shù)據(jù)若贮。#4 就是還不能發(fā)送的數(shù)據(jù)坞笙。
然后此時收到了 36 的 ACK烫葬,并且發(fā)出了 46-51 的字節(jié)院喜,于是窗口向右滑動了场刑。
TCP/IP Guide 上還有一張完整的圖鳄乏,畫的十分清晰蔽豺,大家看一下区丑。
如果接收方回復的窗口一直是 0 怎么辦?
上文已經(jīng)說了發(fā)送方式根據(jù)接收方回應(yīng)的 window 來控制能發(fā)多少數(shù)據(jù)茫虽,如果接收方一直回應(yīng) 0刊苍,那發(fā)送方就杵著?
你想一下濒析,發(fā)送方發(fā)的數(shù)據(jù)都得到 ACK 了正什,但是呢回應(yīng)的窗口都是 0 ,這發(fā)送方此時不敢發(fā)了啊号杏,那也不能一直等著啊婴氮,這 Window 啥時候不變 0 八拱簟?
于是 TCP 有一個 Zero Window Probe 技術(shù)主经,發(fā)送方得知窗口是 0 之后荣暮,會去探測探測這個接收方到底行不行,也就是發(fā)送 ZWP 包給接收方罩驻。
具體看實現(xiàn)了穗酥,可以發(fā)送多次,然后還有間隔時間惠遏,多次之后都不行可以直接 RST砾跃。
假設(shè)接收方每次回應(yīng)窗口都很小怎么辦?
你想象一下节吮,如果每次接收方都說我還能收 1 個字節(jié)抽高,發(fā)送方該不該發(fā)?
TCP + IP 頭部就 40 個字節(jié)了透绩,這傳輸不劃算啊翘骂,如果傻傻的一直發(fā)這就叫 Silly Window。
那咋辦帚豪,一想就是發(fā)送端等著碳竟,等養(yǎng)肥了再發(fā),要么接收端自己自覺點志鞍,數(shù)據(jù)小于一個閾值就告訴發(fā)送端窗口此時是 0 算了瞭亮,也等養(yǎng)肥了再告訴發(fā)送端。
發(fā)送端等著的方案就是納格算法固棚,這個算法相信看一下代碼就知道了统翩。
簡單的說就是當前能發(fā)送的數(shù)據(jù)和窗口大于等于 MSS 就立即發(fā)送,否則再判斷一下之前發(fā)送的包 ACK 回來沒此洲,回來再發(fā)厂汗,不然就攢數(shù)據(jù)。
接收端自覺點的方案是 David D Clark’s 方案呜师,如果窗口數(shù)據(jù)小于某個閾值就告訴發(fā)送方窗口 0 別發(fā)娶桦,等緩過來數(shù)據(jù)大于等于 MSS 或者接受 buffer 騰出一半空間了再設(shè)置正常的 window 值給發(fā)送方。
對了提到納格算法不得不再提一下延遲確認汁汗,納格算法在等待接收方的確認衷畦,而開啟延遲確認則會延遲發(fā)送確認,會等之后的包收到了再一起確認或者等待一段時候真的沒了再回復確認知牌。
這就相互等待了祈争,然后延遲就很大了,兩個不可同時開啟角寸。
已經(jīng)有滑動窗口了為什么還要擁塞控制菩混?
前面我已經(jīng)提到了忿墅,加了擁塞控制是因為 TCP 不僅僅就管兩端之間的情況,還需要知曉一下整體的網(wǎng)絡(luò)情形沮峡,畢竟只有大家都守規(guī)矩了道路才會通暢疚脐。
前面我們提到了重傳,如果不管網(wǎng)絡(luò)整體的情況邢疙,肯定就是對方?jīng)]給 ACK 棍弄,那我就無腦重傳。
如果此時網(wǎng)絡(luò)狀況很差秘症,所有的連接都這樣無腦重傳照卦,是不是網(wǎng)絡(luò)情況就更差了式矫,更加擁堵了乡摹?
然后越擁堵越重傳,一直沖沖沖采转!然后就 GG 了聪廉。
所以需要個擁塞控制,來避免這種情況的發(fā)送故慈。
擁塞控制怎么搞板熊?
主要有以下幾個步驟來搞:
1、慢啟動察绷,探探路干签。
2、擁塞避免拆撼,感覺差不多了減速看看
3容劳、擁塞發(fā)生快速重傳/恢復
慢啟動,就是新司機上路慢慢來闸度,初始化 cwnd(Congestion Window)為 1竭贩,然后每收到一個 ACK 就 cwnd++ 并且每過一個 RTT ,cwnd = 2*cwnd 莺禁。
線性中帶著指數(shù)留量,指數(shù)中又夾雜著線性增。
然后到了一個閾值哟冬,也就是 ssthresh(slow start threshold)的時候就進入了擁塞避免階段楼熄。
這個階段是每收到一個 ACK 就 cwnd = cwnd + 1/cwnd并且每一個 RTT 就 cwnd++。
可以看到都是線性增浩峡。
然后就是一直增可岂,直到開始丟包的情況發(fā)生,前面已經(jīng)分析到重傳有兩種红符,一種是超時重傳青柄,一種是快速重傳伐债。
如果發(fā)生超時重傳的時候,那說明情況有點糟糕致开,于是直接把 ssthresh 置為當前 cwnd 的一半峰锁,然后 cwnd 直接變?yōu)?1,進入慢啟動階段双戳。
如果是快速重傳虹蒋,那么這里有兩種實現(xiàn),一種是 TCP Tahoe 飒货,和超時重傳一樣的處理魄衅。
一種是 TCP Reno,這個實現(xiàn)是把 cwnd = cwnd/2 塘辅,然后把 ssthresh 設(shè)置為當前的 cwnd 晃虫。
然后進入快速恢復階段,將 cwnd = cwnd + 3(因為快速重傳有三次)扣墩,重傳 DACK 指定的包哲银,如果再收到一個DACK則 cwnd++,如果收到是正常的 ACK 那么就將 cwnd 設(shè)為 ssthresh 大小呻惕,進入擁塞避免階段荆责。
可以看到快速恢復就重傳了指定的一個包,那有可能是很多包都丟了亚脆,然后其他的包只能等待超時重傳做院,超時重傳就會導致 cwnd 減半,多次觸發(fā)就指數(shù)級下降濒持。
所以又搞了個 New Reno键耕,多加了個 New,它是在沒有SACK 的情況下改進快速恢復弥喉,它會觀察重傳 DACK 指定的包的響應(yīng) ACK 是否是已經(jīng)發(fā)送的最大 ACK郁竟,比如你發(fā)了1、2由境、3棚亩、4,對方?jīng)]收到 2虏杰,但是 3讥蟆、4都收到了,于是你重傳 2 之后 ACK 肯定是 5纺阔,說明就丟了這一個包瘸彤。
不然就是還有其他包丟了,如果就丟了一個包就是之前的過程一樣笛钝,如果還有其他包丟了就繼續(xù)重傳质况,直到 ACK 是全部的之后再退出快速恢復階段愕宋。
簡單的說就是一直探測到全部包都收到了再結(jié)束這個環(huán)節(jié)。
還有個 FACK结榄,它是基于 SACK 用來作為重傳過程中的擁塞控制中贝,相對于上面的 New Reno 我們就知道它有 SACK 所以不需要一個一個試過去,具體我不展開了臼朗。
還有哪些擁塞控制算法邻寿?
從維基上看有這么多。
本來我還想嗶嗶幾句了视哑,嗶嗶了之后又刪了绣否,感覺說了和沒說一樣,想深入但是實力不允許挡毅,有點惆悵啊蒜撮。
各位看官自個兒查查吧,或者等我日后修煉有成再來嗶嗶慷嗜。
總結(jié)
說了這么多來總結(jié)一下吧淀弹。
TCP 是面向連接的,提供可靠庆械、有序的傳輸并且還提供流控和擁塞控制,單獨提取出 TCP 層而不是在 IP層實現(xiàn)是因為 IP 層有更多的設(shè)備需要使用菌赖,加了復雜的邏輯不劃算缭乘。
三次握手主要是為了定義初始序列號為了之后的傳輸打下基礎(chǔ),四次揮手是因為 TCP 是全雙工協(xié)議琉用,因此雙方都得說拜拜堕绩。
SYN 超時了就階梯性重試,如果有 SYN攻擊邑时,可以加大半隊列數(shù)奴紧,或減少重試次數(shù),或直接拒絕晶丘。
TIME_WAIT 是怕對方?jīng)]收到最后一個 ACK黍氮,然后又發(fā)了 FIN 過來,并且也是等待處理網(wǎng)絡(luò)上殘留的數(shù)據(jù)浅浮,怕影響新連接沫浆。
TIME_WAIT 不建議設(shè)小,或者破壞 TIME_WAIT 機制滚秩,如果真想那么可以開啟快速回收专执,或者重用,不過注意受益的對象郁油。
超時重傳是為了保證對端一定能收到包本股,快速重傳是為了避免在偶爾丟包的時候需要等待超時這么長時間攀痊,SACK 是為了讓發(fā)送方知道重傳哪些。
D-SACK 是為了讓發(fā)送方知道這次重傳的原因是對方真的沒收到還是自己太心急了 RTO 整小了拄显,不至于兩眼一抹黑蚕苇。
滑動窗口是為了平衡發(fā)送方的發(fā)送速率和接收方的接受數(shù)率,不至于瞎發(fā)凿叠,當然還需要注意 Silly Window 的情況涩笤,同時還要注意納格算法和延遲確認不能一起搭配。
而滑動窗口還不夠盒件,還得有個擁塞控制蹬碧,因為出行你我他,安全靠大家炒刁,TCP 還得跳出來看看關(guān)心下當前大局勢恩沽。
最后
至此就差不多了,不過還是有很多很多細節(jié)的翔始,TCP 協(xié)議太復雜了罗心,這可能是我文章里面圖畫的最少的一篇了,你看復雜到我圖都畫不來了哈哈哈城瞎。
今天我就說了個皮毛渤闷,如有紕漏請趕緊后臺聯(lián)系鞭撻我。
巨人的肩膀
http://www.tcpipguide.com/
https://www.ionos.com/digitalguide/server/know-how/introduction-to-tcp/
https://www.ibm.com/developerworks/cn/linux/l-tcp-sack/
https://coolshell.cn/articles/11564.html/
https://tools.ietf.org/html/rfc793
https://nmap.org/book/tcpip-ref.html
我是 yes脖镀,從一點點到億點點飒箭,我們下篇見。