萬字長文 | 23 個問題 TCP 疑難雜症全解析

每一個時代,都不會虧待會學習的人。html

你們好,我是 yes。java

在進入今天主題以前我先拋幾個問題,這篇文章一共提出 23 個問題。linux

TCP 握手必定是三次?TCP 揮手必定是四次?git

爲何要有快速重傳,超時重傳不夠用?爲何要有 SACK,爲何要有 D-SACK?web

都知道有滑動窗口,那因爲接收方的太忙了滑動窗口降爲了 0 怎麼辦?發送方就永遠等着了?算法

Silly Window 又是什麼?shell

爲何有滑動窗口流控還須要擁塞控制?安全

快速重傳必定要依賴三次重複 ACK ?服務器

這篇文章我想由淺到深地過一遍 TCP,不是生硬的搬出各個知識點,從問題入手,而後從發展、演進的角度來看 TCP微信

起初我在學計算機網絡的時候就有很是很是多的疑問,腦子裏簡直充滿了十萬個爲何,而網絡又很是的複雜,發展了這麼多年東西真的太多了,今天我就大體的淺顯地說一說我對 TCP 這些要點的理解

好了,廢話很少說,開始上正菜。

TCP 是用來解決什麼問題?

TCP 即 Transmission Control Protocol,能夠看到是一個傳輸控制協議,重點就在這個控制

控制什麼?

控制可靠、按序地傳輸以及端與端之間的流量控制。夠了麼?還不夠,它須要更加智能,所以還須要加個擁塞控制,須要爲總體網絡的狀況考慮。

這就是出行你我他,安全靠你們

爲何要 TCP,IP 層實現控制不行麼?

咱們知道網絡是分層實現的,網絡協議的設計就是爲了通訊,從鏈路層到 IP 層其實就已經能夠完成通訊了。

你看鏈路層不可或缺畢竟我們電腦都是經過鏈路相互鏈接的,而後 IP 充當了地址的功能,因此經過 IP 我們找到了對方就能夠進行通訊了。

那加個 TCP 層幹啥?IP 層實現控制不就完事了嘛?

之因此要提取出一個 TCP 層來實現控制是由於 IP 層涉及到的設備更多,一條數據在網絡上傳輸須要通過不少設備,而設備之間須要靠 IP 來尋址。

假設 IP 層實現了控制,那是否是涉及到的設備都須要關心不少事情?總體傳輸的效率是否是大打折扣了?

我舉個例子,假如 A 要傳輸給 F 一個積木,可是沒法直接傳輸到,須要通過 B、C、D、E 這幾個中轉站之手。這裏有兩種狀況:

  • 假設 BCDE 都須要關心這個積木搭錯了沒,都拆開包裹仔細的看看,沒問題了再裝回去,最終到了 F 的手中。
  • 假設 BCDE 都不關心積木的狀況,來啥包裹只管轉發就完事了,由最終的 F 本身來檢查這個積木答錯了沒。

你以爲哪一種效率高?明顯是第二種,轉發的設備不須要關心這些事,只管轉發就完事!

因此把控制的邏輯獨立出來成 TCP 層,讓真正的接收端來處理,這樣網絡總體的傳輸效率就高了。

鏈接究竟是什麼?

咱們已經知道了爲何須要獨立出 TCP 這一層,而且這一層主要是用來幹嗎的,接下來就來看看它究竟是怎麼幹的。

咱們都知道 TCP 是面向鏈接的,那這個鏈接究竟是個什麼東西?真的是拉了一條線讓端與端之間連起來了?

所謂的鏈接其實只是雙方都維護了一個狀態,經過每一次通訊來維護狀態的變動,使得看起來好像有一條線關聯了對方。

TCP 協議頭

在具體深刻以前咱們須要先來看看一些 TCP 頭的格式,這很基礎也很重要。

圖來自網絡

我就不一一解釋了,挑重點的說。

首先能夠看到 TCP 包只有端口,沒有 IP。

Seq 就是 Sequence Number 即序號,它是用來解決亂序問題的。

ACK 就是 Acknowledgement Numer 即確認號,它是用來解決丟包狀況的,告訴發送方這個包我收到啦。

標誌位就是 TCP flags 用來標記這個包是什麼類型的,用來控制 TPC 的狀態。

窗口就是滑動窗口,Sliding Window,用來流控。

三次握手

明確了協議頭的要點以後,咱們再來看三次握手。

三次握手真是個老生常談的問題了,可是真的懂了麼?不是浮在表面?能不能延伸出一些點別的?

咱們先來看一下熟悉的流程。

圖來自網絡

首先爲何要握手,其實主要就是爲了初始化Seq Numer,SYN 的全稱是 Synchronize Sequence Numbers,這個序號是用來保證以後傳輸數據的順序性。

你要說是爲了測試保證雙方發送接收功能都正常,我以爲也沒毛病,不過我認爲重點在於同步序號

那爲何要三次,就拿我和你這兩個角色來講,首先我告訴你個人初始化序號,你聽到了和我說你收到了。

而後你告訴我你的初始序號,而後我對你說我收到了。

這好像四次了?若是真的按一來一回就是四次,可是中間一步能夠合在一塊兒,就是你和我說你知道了個人初始序號的時候同時將你的初始序號告訴我。

所以四次握手就能夠減到三次了。

不過你沒有想過這麼一種情形,我和你同時開口,一塊兒告訴對方各自的初始序號,而後分別迴應收到了,這不就是四次握手了?

我來畫個圖,清晰一點。

看看是否是四次握手了? 不過具體仍是得看實現,有些實現可能不容許這種狀況出現,可是這不影響咱們思考,由於握手的重點就是同步初始序列號,這種狀況也完成了同步的目標。

初始序列號 ISN 的取值

不知道你們有沒有想過 ISN 的值要設成什麼?代碼寫死從零開始?

想象一下若是寫死一個值,好比 0 ,那麼假設已經創建好鏈接了,client 也發了不少包好比已經第 20 個包了,而後網絡斷了以後 client 從新,端口號仍是以前那個,而後序列號又從 0 開始,此時服務端返回第 20 個包的ack,客戶端是否是傻了?

因此 RFC793 中認爲 ISN 要和一個假的時鐘綁定在一塊兒ISN 每四微秒加一,當超過 2 的 32 次方以後又從 0 開始,要四個半小時左右發生 ISN 迴繞

因此 ISN 變成一個遞增值,真實的實現還須要加一些隨機值在裏面,防止被不法份子猜到 ISN。

SYN 超時了怎麼處理?

也就是 client 發送 SYN 至 server 而後就掛了,此時 server 發送 SYN+ACK 就一直得不到回覆,怎麼辦?

我腦海中一想到的就是重試,可是不能連續快速重試屢次,你想一下,假設 client 掉線了,你總得給它點時間恢復吧,因此呢須要慢慢重試,階梯性重試

在 Linux 中就是默認重試 5 次,而且就是階梯性的重試,間隔就是1s、2s、4s、8s、16s,再第五次發出以後還得等 32s 才能知道此次重試的結果,因此說總共等63s 才能斷開鏈接。

SYN Flood 攻擊

你看到沒 SYN 超時須要耗費服務端 63s 的時間斷開鏈接,也就說 63s 內服務端須要保持這個資源,因此不法分子就能夠構造出大量的 client 向 server 發 SYN 但就是不回 server。

圖來自網絡

使得 server 的 SYN 隊列耗盡,沒法處理正常的建連請求。

因此怎麼辦?

能夠開啓 tcp_syncookies,那就用不到 SYN 隊列了。

SYN 隊列滿了以後 TCP 根據本身的 ip、端口、而後對方的 ip、端口,對方 SYN 的序號,時間戳等一波操做生成一個特殊的序號(即 cookie)發回去,若是對方是正常的 client 會把這個序號發回來,而後 server 根據這個序號建連。

或者調整 tcp_synack_retries 減小重試的次數,設置 tcp_max_syn_backlog 增長 SYN 隊列數,設置 tcp_abort_on_overflow SYN 隊列滿了直接拒絕鏈接。

爲何要四次揮手?

四次揮手和三次握手成雙成對,一樣也是 TCP 中的一線明星,讓咱們重溫一下熟悉的圖。

圖來自網絡

爲何揮手須要四次?由於 TCP 是全雙工協議,也就是說雙方都要關閉,每一方都向對方發送 FIN 和迴應 ACK。

就像我對你說我數據發完了,而後你回覆好的你收到了。而後你對我說你數據發完了,而後我向你回覆我收到了。

因此看起來就是四次。

從圖中能夠看到主動關閉方的狀態是 FIN_WAIT_1 到 FIN_WAIT_2 而後再到 TIME_WAIT,而被動關閉方是 CLOSE_WAIT 到 LAST_ACK。

四次揮手狀態必定是這樣變遷的嗎

狀態必定是這樣變遷的嗎?讓咱們再來看個圖。

圖來自網絡

能夠看到雙方都主動發起斷開請求因此各自都是主動發起方,狀態會從 FIN_WAIT_1 都進入到 CLOSING 這個過分狀態而後再到 TIME_WAIT。

揮手必定須要四次嗎?

假設 client 已經沒有數據發送給 server 了,因此它發送 FIN 給 server 代表本身數據發完了,再也不發了,若是這時候 server 仍是有數據要發送給 client 那麼它就是先回復 ack ,而後繼續發送數據。

等 server 數據發送完了以後再向 client 發送 FIN 代表它也發完了,而後等 client 的 ACK 這種狀況下就會有四次揮手。

那麼假設 client 發送 FIN 給 server 的時候 server 也沒數據給 client,那麼 server 就能夠將 ACK 和它的 FIN 一塊兒發給client ,而後等待 client 的 ACK,這樣不就三次揮手了?

爲何要有 TIME_WAIT?

斷開鏈接發起方在接受到接受方的 FIN 並回復 ACK 以後並無直接進入 CLOSED 狀態,而是進行了一波等待,等待時間爲 2MSL。

MSL 是 Maximum Segment Lifetime,即報文最長生存時間,RFC 793 定義的 MSL 時間是 2 分鐘,Linux 實際實現是 30s,那麼 2MSL 是一分鐘。

那麼爲何要等 2MSL 呢?

  • 就是怕被動關閉方沒有收到最後的 ACK,若是被動方因爲網絡緣由沒有到,那麼它會再次發送 FIN, 此時若是主動關閉方已經 CLOSED 那就傻了,所以等一下子。

  • 假設立馬斷開鏈接,可是又重用了這個鏈接,就是五元組徹底一致,而且序號還在合適的範圍內,雖然機率很低但理論上也有可能,那麼新的鏈接會被已關閉鏈接鏈路上的一些殘留數據干擾,所以給予必定的時間來處理一些殘留數據。

等待 2MSL 會產生什麼問題?

若是服務器主動關閉大量的鏈接,那麼會出現大量的資源佔用,須要等到 2MSL 纔會釋放資源。

若是是客戶端主動關閉大量的鏈接,那麼在 2MSL 裏面那些端口都是被佔用的,端口只有 65535 個,若是端口耗盡了就沒法發起送的鏈接了,不過我以爲這個機率很低,這麼多端口你這是要創建多少個鏈接?

如何解決 2MSL 產生的問題?

快速回收,即不等 2MSL 就回收, Linux 的參數是 tcp_tw_recycle,還有 tcp_timestamps 不過默認是打開的。

其實上面咱們已經分析過爲何須要等 2MSL,因此若是等待時間果斷就是出現上面說的那些問題。

因此不建議開啓,並且 Linux 4.12 版本後已經咔擦了這個參數了。

前不久剛有位朋友在羣裏就提到了這玩意。

一問果真有 NAT 的身影。

現象就是請求端請求服務器的靜態資源偶爾會出現 20-60 秒左右纔會有響應的狀況,從抓包看請求端連續三個 SYN 都沒有迴應。

好比你在學校,對外可能就一個公網 IP,而後開啓了 tcp_tw_recycle(tcp_timestamps 也是打開的狀況下),在 60 秒內對於同源 IP 的鏈接請求中 timestamp 必須是遞增的,否則認爲其是過時的數據包就會丟棄。

學校這麼多機器,你沒法保證時間戳是一致的,所以就會出問題。

因此這玩意不推薦使用。

重用,即開啓 tcp_tw_reuse 固然也是須要 tcp_timestamps 的。

這裏有個重點,tcp_tw_reuse 是用在鏈接發起方的,而咱們的服務端基本上是鏈接被動接收方

tcp_tw_reuse 是發起新鏈接的時候,能夠複用超過 1s 的處於 TIME_WAIT 狀態的鏈接,因此它壓根沒有減小咱們服務端的壓力。

它重用的是發起方處於 TIME_WAIT 的鏈接

這裏還有一個 SO_REUSEADDR ,這玩意有人會和 tcp_tw_reuse 混爲一談,首先 tcp_tw_reuse 是內核選項而 SO_REUSEADDR 是用戶態選項。

而後 SO_REUSEADDR 主要用在你啓動服務的時候,若是此時的端口被佔用了而且這個鏈接處於 TIME_WAIT 狀態,那麼你能夠重用這個端口,若是不是 TIME_WAIT,那就是給你個 Address already in use。

因此這兩個玩意好像都不行,並且 tcp_tw_reuse 和tcp_tw_recycle,實際上是違反 TCP 協議的,說好的等我到天荒地老,你卻偷偷放了手?

要麼就是調小 MSL 的時間,不過也不太安全,要麼調整 tcp_max_tw_buckets 控制 TIME_WAIT 的數量,不過默認值已經很大了 180000,這玩意應該是用來對抗 DDos 攻擊的。

因此我給出的建議是服務端不要主動關閉,把主動關閉方放到客戶端。畢竟我們服務器是一對不少不少服務,咱們的資源比較寶貴。

本身攻擊本身

還有一個很騷的解決方案,我本身瞎想的,就是本身攻擊本身。

Socket 有一個選項叫 IP_TRANSPARENT ,能夠綁定一個非本地的地址,而後服務端把建連的 ip 和端口都記下來,好比寫入本地某個地方。

而後啓動一個服務,假如如今服務端資源很緊俏,那麼你就定個時間,過了多久以後就將處於 TIME_WAIT 狀態的對方 ip 和端口告訴這個服務。

而後這個服務就利用 IP_TRANSPARENT 假裝成以前的那個 client 向服務端發起一個請求,而後服務端收到會給真的 client 一個 ACK, 那 client 都關了已經,說你在搞啥子,因而回了一個 RST,而後服務端就停止了這個鏈接。

超時重傳機制是爲了解決什麼問題?

前面咱們提到 TCP 要提供可靠的傳輸,那麼網絡又是不穩定的若是傳輸的包對方沒收到卻又得保證可靠那麼就必須重傳。

TCP 的可靠性是靠確認號的,好比我發給你一、二、三、4這4個包,你告訴我你如今要 5 那說明前面四個包你都收到了,就是這麼回事兒。

不過這裏要注意,SeqNum 和 ACK 都是以字節數爲單位的,也就是說假設你收到了一、二、4 可是 3 沒有收到你不能 ACK 5,若是你回了 5 那麼發送方就覺得你5以前的都收到了。

因此只能回覆確認最大連續收到包,也就是 3。

而發送方不清楚 三、4 這兩個包究竟是還沒到呢仍是已經丟了,因而發送方須要等待,這等待的時間就比較講究了。

若是太心急可能 ACK 已經在路上了,你這重傳就是浪費資源了,若是太散漫,那麼接收方急死了,這死鬼怎麼還不發包來,我等的花兒都謝了。

因此這個等待超時重傳的時間很關鍵,怎麼搞?聰明的小夥伴可能一下就想到了,你估摸着正常來回一趟時間是多少不就行了,我就等這麼長。

這就來回一趟的時間就叫 RTT,即 Round Trip Time,而後根據這個時間制定超時重傳的時間 RTO,即 Retransmission Timeout。

不過這裏大概只好了 RTO 要參考下 RTT ,可是具體要怎麼算?首先確定是採樣,而後一波加權平均獲得 RTO。

RFC793 定義的公式以下:

一、先採樣 RTT 二、SRTT = ( ALPHA * SRTT ) + ((1-ALPHA) * RTT) 三、RTO = min[UBOUND,max[LBOUND,(BETA*SRTT)]]

ALPHA 是一個平滑因子取值在 0.8~0.9之間,UBOUND 就是超時時間上界-1分鐘,LBOUND 是下界-1秒鐘,BETA 是一個延遲方差因子,取值在 1.3~2.0。

可是還有個問題,RTT 採樣的時間用一開始發送數據的時間到收到 ACK 的時間做爲樣本值仍是重傳的時間到 ACK 的時間做爲樣本值?

圖來自網絡

從圖中就能夠看到,一個時間算長了,一個時間算短了,這有點難,由於你不知道這個  ACK 究竟是回覆誰的。

因此怎麼辦?發生重傳的來回我不採樣不就行了,我不知道此次 ACK 究竟是回覆誰的,我就無論他,我就採樣正常的來回。

這就是 Karn / Partridge 算法,不採樣重傳的RTT。

可是不採樣重傳會有問題,好比某一時刻網絡忽然就是不好,你要是無論重傳,那麼仍是按照正常的 RTT 來算 RTO, 那麼超時的時間就太短了,因而在網絡不好的狀況下還瘋狂重傳加劇了網絡的負載。

所以 Karn 算法就很粗暴的搞了個發生重傳我就將如今的 RTO 翻倍,哼!就是這麼簡單粗暴。

可是這種平均的計算很容易把一個忽然間的大波動,平滑掉,因此又搞了個算法,叫 Jacobson / Karels Algorithm。

它把最新的 RTT 和平滑過的 SRTT 作了波計算獲得合適的 RTO,公式我就不貼了,反正我不懂,不懂就不嗶嗶了。

爲何還須要快速重傳機制?

超時重傳是按時間來驅動的,若是是網絡情況真的很差的狀況,超時重傳沒問題,可是若是網絡情況好的時候,只是恰巧丟包了,那等這麼長時間就不必。

因而又引入了數據驅動的重傳叫快速重傳,什麼意思呢?就是發送方若是連續三次收到對方相同的確認號,那麼立刻重傳數據。

由於連續收到三次相同 ACK 證實當前網絡情況是 ok 的,那麼確認是丟包了,因而立馬重發,不必等這麼久。

圖來自網絡

看起來好像挺完美的,可是你有沒有想過我發送一、二、三、4這4個包,就 2 對方沒收到,一、三、4都收到了,而後無論是超時重傳仍是快速重傳反正對方就回 ACK 2。

這時候要重傳 二、三、4 呢仍是就 2 呢?

SACK 的引入是爲了解決什麼問題?

SACK 即 Selective Acknowledgment,它的引入就是爲了解決發送方不知道該重傳哪些數據的問題。

咱們來看一下下面的圖就知道了。

圖來自網絡

SACK 就是接收方會回傳它已經接受到的數據,這樣發送方就知道哪一些數據對方已經收到了,因此就能夠選擇性的發送丟失的數據。

如圖,經過 ACK 告知我接下來要 5500 開始的數據,並一直更新 SACK,6000-6500 我收到了,6000-7000的數據我收到了,6000-7500的數據我收到了,發送方很明確的知道,5500-5999 的那一波數據應該是丟了,因而重傳。

並且若是數據是多段不連續的, SACK 也能夠發送,好比 SACK 0-500,1000-1500,2000-2500。就代表這幾段已經收到了。

D-SACK 又是什麼東西?

D-SACK 實際上是 SACK 的擴展,它利用 SACK 的第一段來描述重複接受的不連續的數據序號,若是第一段描述的範圍被 ACK 覆蓋,說明重複了,好比我都 ACK 到6000了你還給我回 SACK 5000-5500 呢?

說白了就是從第一段的反饋來和已經接受到的 ACK 比一比,參數是 tcp_dsack,Linux 2.4 以後默認開啓。

那知道重複了有什麼用呢?

一、知道重複了說明對方收到剛纔那個包了,因此是回來的 ACK 包丟了。二、是否是包亂序的,先發的包後到?三、是否是本身太着急了,RTO 過小了?四、是否是被數據複製了,搶先一步呢?

滑動窗口乾嗎用?

咱們已經知道了 TCP 有序號,而且還有重傳,可是這還不夠,由於咱們不是愣頭青,還須要根據狀況來控制一下發送速率,由於網絡是複雜多變的,有時候就會阻塞住,而有時候又很通暢。

因此發送方須要知道接收方的狀況,好控制一下發送的速率,不至於蒙着頭一個勁兒的發而後接受方都接受不過來。

所以 TCP 就有個叫滑動窗口的東西來作流量控制,也就是接收方告訴發送方我還能接受多少數據,而後發送方就能夠根據這個信息來進行數據的發送。

如下是發送方維護的窗口,就是黑色圈起來的。

圖來自網絡

圖中的 #1 是已收到 ACK 的數據,#2 是已經發出去可是還沒收到 ACK 的數據,#3 就是在窗口內能夠發送可是還沒發送的數據。#4 就是還不能發送的數據。

而後此時收到了 36 的 ACK,而且發出了 46-51 的字節,因而窗口向右滑動了。

圖片來自網絡

TCP/IP Guide 上還有一張完整的圖,畫的十分清晰,你們看一下。

若是接收方回覆的窗口一直是 0 怎麼辦?

上文已經說了發送方式根據接收方迴應的 window 來控制能發多少數據,若是接收方一直迴應 0,那發送方就杵着?

你想一下,發送方發的數據都獲得 ACK 了,可是呢迴應的窗口都是 0 ,這發送方此時不敢發了啊,那也不能一直等着啊,這 Window 啥時候不變 0 啊?

因而 TCP 有一個 Zero Window Probe 技術,發送方得知窗口是 0 以後,會去探測探測這個接收方到底行不行,也就是發送 ZWP 包給接收方。

具體看實現了,能夠發送屢次,而後還有間隔時間,屢次以後都不行能夠直接 RST。

假設接收方每次迴應窗口都很小怎麼辦?

你想象一下,若是每次接收方都說我還能收 1 個字節,發送方該不應發?

TCP + IP 頭部就 40 個字節了,這傳輸不划算啊,若是傻傻的一直髮這就叫 Silly Window。

那咋辦,一想就是發送端等着,等養肥了再發,要麼接收端本身自覺點,數據小於一個閾值就告訴發送端窗口此時是 0 算了,也等養肥了再告訴發送端。

發送端等着的方案就是納格算法,這個算法相信看一下代碼就知道了。

簡單的說就是當前能發送的數據和窗口大於等於 MSS 就當即發送,不然再判斷一下以前發送的包 ACK 回來沒,回來再發,否則就攢數據。

接收端自覺點的方案是 David D Clark’s 方案,若是窗口數據小於某個閾值就告訴發送方窗口 0 別發,等緩過來數據大於等於 MSS 或者接受 buffer 騰出一半空間了再設置正常的 window 值給發送方。

對了提到納格算法不得再也不提一下延遲確認,納格算法在等待接收方的確認,而開啓延遲確認則會延遲發送確認,會等以後的包收到了再一塊兒確認或者等待一段時候真的沒了再回復確認。

這就相互等待了,而後延遲就很大了,兩個不可同時開啓。

已經有滑動窗口了爲何還要擁塞控制?

前面我已經提到了,加了擁塞控制是由於 TCP 不只僅就管兩端之間的狀況,還須要知曉一下總體的網絡情形,畢竟只有你們都守規矩了道路纔會通暢。

前面咱們提到了重傳,若是無論網絡總體的狀況,確定就是對方沒給 ACK ,那我就無腦重傳。

若是此時網絡情況不好,全部的鏈接都這樣無腦重傳,是否是網絡狀況就更差了,更加擁堵了?

而後越擁堵越重傳,一直衝沖沖!而後就 GG 了。

因此須要個擁塞控制,來避免這種狀況的發送。

擁塞控制怎麼搞?

主要有如下幾個步驟來搞:

一、慢啓動,探探路。二、擁塞避免,感受差很少了減速看看 三、擁塞發生快速重傳/恢復

慢啓動,就是新司機上路慢慢來,初始化 cwnd(Congestion Window)爲 1,而後每收到一個 ACK 就 cwnd++ 而且每過一個 RTT ,cwnd = 2*cwnd 。

線性中帶着指數,指數中又夾雜着線性增。

而後到了一個閾值,也就是 ssthresh(slow start threshold)的時候就進入了擁塞避免階段。

這個階段是每收到一個 ACK 就 cwnd = cwnd + 1/cwnd而且每個 RTT 就 cwnd++。

能夠看到都是線性增。

而後就是一直增,直到開始丟包的狀況發生,前面已經分析到重傳有兩種,一種是超時重傳,一種是快速重傳。

若是發生超時重傳的時候,那說明狀況有點糟糕,因而直接把 ssthresh 置爲當前 cwnd 的一半,而後 cwnd 直接變爲 1,進入慢啓動階段。

若是是快速重傳,那麼這裏有兩種實現,一種是 TCP Tahoe ,和超時重傳同樣的處理。

一種是 TCP Reno,這個實現是把 cwnd = cwnd/2 ,而後把 ssthresh 設置爲當前的 cwnd 。

而後進入快速恢復階段,將 cwnd = cwnd + 3(由於快速重傳有三次),重傳 DACK 指定的包,若是再收到一個DACK則 cwnd++,若是收到是正常的 ACK 那麼就將 cwnd 設爲 ssthresh 大小,進入擁塞避免階段。

能夠看到快速恢復就重傳了指定的一個包,那有多是不少包都丟了,而後其餘的包只能等待超時重傳,超時重傳就會致使 cwnd 減半,屢次觸發就指數級降低。

因此又搞了個 New Reno,多加了個 New,它是在沒有SACK 的狀況下改進快速恢復,它會觀察重傳 DACK 指定的包的響應 ACK 是不是已經發送的最大 ACK,好比你發了一、二、三、4,對方沒收到 2,可是 三、4都收到了,因而你重傳 2 以後 ACK 確定是 5,說明就丟了這一個包。

否則就是還有其餘包丟了,若是就丟了一個包就是以前的過程同樣,若是還有其餘包丟了就繼續重傳,直到 ACK 是所有的以後再退出快速恢復階段。

簡單的說就是一直探測到所有包都收到了再結束這個環節。

還有個 FACK,它是基於 SACK 用來做爲重傳過程當中的擁塞控制,相對於上面的 New Reno 咱們就知道它有 SACK 因此不須要一個一個試過去,具體我不展開了。

還有哪些擁塞控制算法?

從維基上看有這麼多。

原本我還想嗶嗶幾句了,嗶嗶了以後又刪了,感受說了和沒說同樣,想深刻可是實力不容許,有點惆悵啊。

各位看官自個兒查查吧,或者等我往後修煉有成再來嗶嗶。

總結

說了這麼多來總結一下吧。

TCP 是面向鏈接的,提供可靠、有序的傳輸而且還提供流控和擁塞控制,單獨提取出 TCP 層而不是在 IP層實現是由於 IP 層有更多的設備須要使用,加了複雜的邏輯不划算。

三次握手主要是爲了定義初始序列號爲了以後的傳輸打下基礎,四次揮手是由於 TCP 是全雙工協議,所以雙方都得說拜拜。

SYN 超時了就階梯性重試,若是有 SYN攻擊,能夠加大半隊列數,或減小重試次數,或直接拒絕。

TIME_WAIT 是怕對方沒收到最後一個 ACK,而後又發了 FIN 過來,而且也是等待處理網絡上殘留的數據,怕影響新鏈接。

TIME_WAIT 不建議設小,或者破壞 TIME_WAIT 機制,若是真想那麼能夠開啓快速回收,或者重用,不過注意受益的對象。

超時重傳是爲了保證對端必定能收到包,快速重傳是爲了不在偶爾丟包的時候須要等待超時這麼長時間,SACK 是爲了讓發送方知道重傳哪些。

D-SACK 是爲了讓發送方知道此次重傳的緣由是對方真的沒收到仍是本身太心急了 RTO 整小了,不至於兩眼一抹黑。

滑動窗口是爲了平衡發送方的發送速率和接收方的接受數率,不至於瞎發,固然還須要注意 Silly Window 的狀況,同時還要注意納格算法和延遲確認不能一塊兒搭配。

而滑動窗口還不夠,還得有個擁塞控制,由於出行你我他,安全靠你們,TCP 還得跳出來看看關心下當前大局勢。

最後

至此就差很少了,不過仍是有不少不少細節的,TCP 協議太複雜了,這多是我文章裏面圖畫的最少的一篇了,你看複雜到我圖都畫不來了哈哈哈。

今天我就說了個皮毛,若有紕漏請趕忙後臺聯繫鞭撻我。

巨人的肩膀

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/rfc793https://nmap.org/book/tcpip-ref.html


我是 yes,從一點點到億點點,咱們下篇見

本文分享自微信公衆號 - yes的練級攻略(yes_java)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索