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

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個時代,都不會虧待會學習的人。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在進入今天主題之前我先拋幾個問題,這篇文章一共提出 23 個問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP 握手一定是三次?TCP 揮手一定是四次?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼要有快速重傳,超時重傳不夠用?爲什麼要有 SACK,爲什麼要有 D-SACK?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"都知道有滑動窗口,那由於接收方的太忙了滑動窗口降爲了 0 怎麼辦?發送方就永遠等着了?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Silly Window 又是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼有滑動窗口流控還需要擁塞控制?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"快速重傳一定要依賴三次重複 ACK ?"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這篇文章我想"},{"type":"text","marks":[{"type":"strong"}],"text":"由淺到深地過一遍 TCP,不是生硬的搬出各個知識點,從問題入手,然後從發展、演進的角度來看 TCP"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"起初我在學計算機網絡的時候就有非常非常多的疑問,腦子裏簡直充滿了十萬個爲什麼,而網絡又非常的複雜,發展了這麼多年東西真的太多了,今天我就"},{"type":"text","marks":[{"type":"strong"}],"text":"大致的淺顯地說一說我對 TCP 這些要點的理解"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"好了,廢話不多說,開始上正菜。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"TCP 是用來解決什麼問題?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP 即 Transmission Control Protocol,可以看到是一個傳輸控制協議,重點就在這個"},{"type":"text","marks":[{"type":"strong"}],"text":"控制"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"控制什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"控制可靠、按序地傳輸以及端與端之間的流量控制。夠了麼?還不夠,它需要更加智能,因此還需要加個擁塞控制,需要爲整體網絡的情況考慮。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就是"},{"type":"text","marks":[{"type":"strong"}],"text":"出行你我他,安全靠大家"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"爲什麼要 TCP,IP 層實現控制不行麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們知道網絡是分層實現的,網絡協議的設計就是爲了通信,從鏈路層到 IP 層其實就已經可以完成通信了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你看鏈路層不可或缺畢竟咱們電腦都是通過鏈路相互連接的,然後 IP 充當了地址的功能,所以通過 IP 咱們找到了對方就可以進行通信了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那加個 TCP 層幹啥?IP 層實現控制不就完事了嘛?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"之所以要"},{"type":"text","marks":[{"type":"strong"}],"text":"提取出一個 TCP 層來實現控制是因爲 IP 層涉及到的設備更多"},{"type":"text","text":",一條數據在網絡上傳輸需要經過很多設備,而設備之間需要靠 IP 來尋址。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設 IP 層實現了控制,那是不是涉及到的設備都需要關心很多事情?整體傳輸的效率是不是大打折扣了?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cf/cff7ee243e039a3dc0973f9105170060.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我舉個例子,假如 A 要傳輸給 F 一個積木,但是無法直接傳輸到,需要經過 B、C、D、E 這幾個中轉站之手。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏有兩種情況:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設 BCDE 都需要關心這個積木搭錯了沒,都拆開包裹仔細的看看,沒問題了再裝回去,最終到了 F 的手中。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設 BCDE 都不關心積木的情況,來啥包裹只管轉發就完事了,由最終的 F 自己來檢查這個積木答錯了沒。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你覺得哪種效率高?明顯是第二種,轉發的設備不需要關心這些事,只管轉發就完事!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以把控制的邏輯獨立出來成 TCP 層,讓真正的接收端來處理,這樣網絡整體的傳輸效率就高了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"連接到底是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們已經知道了爲什麼需要獨立出 TCP 這一層,並且這一層主要是用來幹嘛的,接下來就來看看它到底是怎麼幹的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們都知道 TCP 是面向連接的,那這個連接到底是個什麼東西?真的是拉了一條線讓端與端之間連起來了?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"所謂的連接其實只是雙方都維護了一個狀態,通過每一次通信來維護狀態的變更"},{"type":"text","text":",使得看起來好像有一條線關聯了對方。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"TCP 協議頭"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在具體深入之前我們需要先來看看一些 TCP 頭的格式,這很基礎也很重要。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d9/d9934530c6c334f500cbb92bdefb3b2d.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我就不一一解釋了,挑重點的說。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先可以看到 TCP 包只有端口,沒有 IP。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Seq 就是 Sequence Number 即序號,它是用來解決亂序問題的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ACK 就是 Acknowledgement Numer 即確認號,它是用來解決丟包情況的,告訴發送方這個包我收到啦。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"標誌位就是 TCP flags 用來標記這個包是什麼類型的,用來控制 TPC 的狀態。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"窗口就是滑動窗口,Sliding Window,用來流控。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三次握手"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"明確了協議頭的要點之後,我們再來看三次握手。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"三次握手真是個老生常談的問題了,但是真的懂了麼?不是浮在表面?能不能延伸出一些點別的?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們先來看一下熟悉的流程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/24/245aae771ceef4457f8f2892f4ba8e13.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先爲什麼要握手,"},{"type":"text","marks":[{"type":"strong"}],"text":"其實主要就是爲了初始化Seq Numer"},{"type":"text","text":",SYN 的全稱是 Synchronize Sequence Numbers,這個序號是用來保證之後傳輸數據的順序性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你要說是爲了測試保證雙方發送接收功能都正常,我覺得也沒毛病,"},{"type":"text","marks":[{"type":"strong"}],"text":"不過我認爲重點在於同步序號"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那爲什麼要三次,就拿我和你這兩個角色來說,首先我告訴你我的初始化序號,你聽到了和我說你收到了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後你告訴我你的初始序號,然後我對你說我收到了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這好像四次了?如果真的按一來一回就是四次,但是中間一步可以合在一起,就是你和我說你知道了我的初始序號的時候同時將你的初始序號告訴我。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此四次握手就可以減到三次了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不過你沒有想過這麼一種情形,我和你同時開口,一起告訴對方各自的初始序號,然後分別迴應收到了,這不就是四次握手了?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我來畫個圖,清晰一點。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/79/796a95c3266e7a2cf0b313342312694e.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看看是不是四次握手了? 不過具體還是得看實現,有些實現可能不允許這種情況出現,但是這不影響我們思考,因爲"},{"type":"text","marks":[{"type":"strong"}],"text":"握手的重點就是同步初始序列號"},{"type":"text","text":",這種情況也完成了同步的目標。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"初始序列號 ISN 的取值"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不知道大家有沒有想過 ISN 的值要設成什麼?代碼寫死從零開始?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"想象一下如果寫死一個值,比如 0 ,那麼假設已經建立好連接了,client 也發了很多包比如已經第 20 個包了,然後網絡斷了之後 client 重新,端口號還是之前那個,然後序列號又從 0 開始,此時服務端返回第 20 個包的ack,客戶端是不是傻了?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以 RFC793 中認爲 ISN 要和一個假的時鐘綁定在一起"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"ISN 每四微秒加一,當超過 2 的 32 次方之後又從 0 開始,要四個半小時左右發生 ISN 迴繞"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以 ISN 變成一個遞增值,真實的實現還需要加一些隨機值在裏面,防止被不法份子猜到 ISN。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"SYN 超時了怎麼處理?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是 client 發送 SYN 至 server 然後就掛了,此時 server 發送 SYN+ACK 就一直得不到回覆,怎麼辦?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我腦海中一想到的就是重試,但是不能連續快速重試多次,你想一下,假設 client 掉線了,你總得給它點時間恢復吧,所以呢需要"},{"type":"text","marks":[{"type":"strong"}],"text":"慢慢重試,階梯性重試"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Linux 中就是默認重試 5 次,並且就是階梯性的重試,間隔就是1s、2s、4s、8s、16s,再第五次發出之後還得等 32s 才能知道這次重試的結果,所以說總共等63s 才能斷開連接。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"SYN Flood 攻擊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你看到沒 SYN 超時需要耗費服務端 63s 的時間斷開連接,也就說 63s 內服務端需要保持這個資源,所以不法分子就可以構造出大量的 client 向 server 發 SYN 但就是不回 server。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/94/940d0f239c64476df823ef7c5be0dab0.png","alt":"圖來自網絡","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使得 server 的 SYN 隊列耗盡,無法處理正常的建連請求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以怎麼辦?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以開啓 tcp_syncookies,那就用不到 SYN 隊列了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SYN 隊列滿了之後 TCP 根據自己的 ip、端口、然後對方的 ip、端口,對方 SYN 的序號,時間戳等一波操作生成一個特殊的序號(即 cookie)發回去,如果對方是正常的 client 會把這個序號發回來,然後 server 根據這個序號建連。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"或者調整 tcp"},{"type":"text","marks":[{"type":"italic"}],"text":"synack"},{"type":"text","text":"retries 減少重試的次數,設置 tcp"},{"type":"text","marks":[{"type":"italic"}],"text":"max"},{"type":"text","text":"syn_backlog 增加 SYN 隊列數,設置 tcp_abort"},{"type":"text","marks":[{"type":"italic"}],"text":"on"},{"type":"text","text":"overflow SYN 隊列滿了直接拒絕連接。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"爲什麼要四次揮手?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"四次揮手和三次握手成雙成對,同樣也是 TCP 中的一線明星,讓我們重溫一下熟悉的圖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0e/0edf1401d68dd9dce9006ffe72462a94.png","alt":"圖來自網絡","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼揮手需要四次?"},{"type":"text","marks":[{"type":"strong"}],"text":"因爲 TCP 是全雙工協議"},{"type":"text","text":",也就是說雙方都要關閉,每一方都向對方發送 FIN 和迴應 ACK。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"就像我對你說我數據發完了,然後你回覆好的你收到了。然後你對我說你數據發完了,然後我向你回覆我收到了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以看起來就是四次。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從圖中可以看到主動關閉方的狀態是 FIN"},{"type":"text","marks":[{"type":"italic"}],"text":"WAIT"},{"type":"text","text":"1 到 FIN"},{"type":"text","marks":[{"type":"italic"}],"text":"WAIT"},{"type":"text","text":"2 然後再到 TIME"},{"type":"text","marks":[{"type":"italic"}],"text":"WAIT,而被動關閉方是 CLOSE"},{"type":"text","text":"WAIT 到 LAST_ACK。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"四次揮手狀態一定是這樣變遷的嗎"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"狀態一定是這樣變遷的嗎?讓我們再來看個圖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e0/e0586dc2a5ce9b8c55e32ba482dc264f.png","alt":"圖來自網絡","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到雙方都主動發起斷開請求所以各自都是主動發起方,狀態會從 FIN"},{"type":"text","marks":[{"type":"italic"}],"text":"WAIT"},{"type":"text","text":"1 都進入到 CLOSING 這個過度狀態然後再到 TIME_WAIT。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"揮手一定需要四次嗎?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設 client 已經沒有數據發送給 server 了,所以它發送 FIN 給 server 表明自己數據發完了,不再發了,如果這時候 server 還是有數據要發送給 client 那麼它就是先回復 ack ,然後繼續發送數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"等 server 數據發送完了之後再向 client 發送 FIN 表明它也發完了,然後等 client 的 ACK 這種情況下就會有四次揮手。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼假設 client 發送 FIN 給 server 的時候 server 也沒數據給 client,那麼 server 就可以將 ACK 和它的 FIN 一起發給client ,然後等待 client 的 ACK,這樣不就三次揮手了?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":" 爲什麼要有 TIME_WAIT?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"斷開連接發起方在接受到接受方的 FIN 並回復 ACK 之後並沒有直接進入 CLOSED 狀態,而是進行了一波等待,等待時間爲 2MSL。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"MSL 是 Maximum Segment Lifetime,即報文最長生存時間,RFC 793 定義的 MSL 時間是 2 分鐘,Linux 實際實現是 30s,那麼 2MSL 是一分鐘。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"那麼爲什麼要等 2MSL 呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"就是怕被動關閉方沒有收到最後的 ACK,如果被動方由於網絡原因沒有到,那麼它會再次發送 FIN, 此時如果主動關閉方已經 CLOSED 那就傻了,因此等一會兒。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設立馬斷開連接,但是又重用了這個連接,就是五元組完全一致,並且序號還在合適的範圍內,雖然概率很低但理論上也有可能,那麼新的連接會被已關閉連接鏈路上的一些殘留數據干擾,因此給予一定的時間來處理一些殘留數據。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"等待 2MSL 會產生什麼問題?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果服務器主動關閉大量的連接,那麼會出現大量的資源佔用,需要等到 2MSL 纔會釋放資源。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果是客戶端主動關閉大量的連接,那麼在 2MSL 裏面那些端口都是被佔用的,端口只有 65535 個,如果端口耗盡了就無法發起送的連接了,不過我覺得這個概率很低,這麼多端口你這是要建立多少個連接?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"如何解決 2MSL 產生的問題?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"快速回收"},{"type":"text","text":",即不等 2MSL 就回收, Linux 的參數是 tcp"},{"type":"text","marks":[{"type":"italic"}],"text":"tw"},{"type":"text","text":"recycle,還有 tcp_timestamps 不過默認是打開的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實上面我們已經分析過爲什麼需要等 2MSL,所以如果等待時間果斷就是出現上面說的那些問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以不建議開啓,而且 Linux 4.12 版本後已經咔擦了這個參數了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前不久剛有位朋友在羣裏就提到了這玩意。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/87/8756d9f0717ecffcef2683fe199bd960.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一問果然有 NAT 的身影。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現象就是請求端請求服務器的靜態資源偶爾會出現 20-60 秒左右纔會有響應的情況,從抓包看請求端連續三個 SYN 都沒有迴應。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如你在學校,對外可能就一個公網 IP,然後開啓了 tcp"},{"type":"text","marks":[{"type":"italic"}],"text":"tw"},{"type":"text","text":"recycle(tcp_timestamps 也是打開的情況下),在 60 秒內對於同源 IP 的連接請求中 timestamp 必須是遞增的,不然認爲其是過期的數據包就會丟棄。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"學校這麼多機器,你無法保證時間戳是一致的,因此就會出問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以這玩意不推薦使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f6/f6956d11bff6bae66fe866809eef0e08.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"重用"},{"type":"text","text":",即開啓 tcp"},{"type":"text","marks":[{"type":"italic"}],"text":"tw"},{"type":"text","text":"reuse 當然也是需要 tcp_timestamps 的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏有個重點,"},{"type":"text","marks":[{"type":"strong"}],"text":"tcp_tw_reuse 是用在連接發起方的,而我們的服務端基本上是連接被動接收方"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"tcp"},{"type":"text","marks":[{"type":"italic"}],"text":"tw"},{"type":"text","text":"reuse 是發起新連接的時候,可以複用超過 1s 的處於 TIME_WAIT 狀態的連接,所以它壓根沒有減少我們服務端的壓力。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"它重用的是發起方處於 TIME_WAIT 的連接"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏還有一個 SO"},{"type":"text","marks":[{"type":"italic"}],"text":"REUSEADDR ,這玩意有人會和 tcp"},{"type":"text","text":"tw_reuse 混爲一談,首先 tcp_tw"},{"type":"text","marks":[{"type":"italic"}],"text":"reuse 是內核選項而 SO"},{"type":"text","text":"REUSEADDR 是用戶態選項。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後 SO"},{"type":"text","marks":[{"type":"italic"}],"text":"REUSEADDR 主要用在你啓動服務的時候,如果此時的端口被佔用了並且這個連接處於 TIME"},{"type":"text","text":"WAIT 狀態,那麼你可以重用這個端口,如果不是 TIME_WAIT,那就是給你個 Address already in use。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以這兩個玩意好像都不行,而且 tcp"},{"type":"text","marks":[{"type":"italic"}],"text":"tw"},{"type":"text","text":"reuse 和tcp"},{"type":"text","marks":[{"type":"italic"}],"text":"tw"},{"type":"text","text":"recycle,其實是違反 TCP 協議的,說好的等我到天荒地老,你卻偷偷放了手?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/de/de451fd539ba33f5854facc8d45bfc65.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要麼就是調小 MSL 的時間,不過也不太安全,要麼調整 tcp"},{"type":"text","marks":[{"type":"italic"}],"text":"max"},{"type":"text","text":"tw_buckets 控制 TIME_WAIT 的數量,不過默認值已經很大了 180000,這玩意應該是用來對抗 DDos 攻擊的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我給出的建議是"},{"type":"text","marks":[{"type":"strong"}],"text":"服務端不要主動關閉,把主動關閉方放到客戶端"},{"type":"text","text":"。畢竟咱們服務器是一對很多很多服務,我們的資源比較寶貴。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"自己攻擊自己"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還有一個很騷的解決方案,我自己瞎想的,就是自己攻擊自己。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Socket 有一個選項叫 IP_TRANSPARENT ,可以綁定一個非本地的地址,然後服務端把建連的 ip 和端口都記下來,比如寫入本地某個地方。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後啓動一個服務,假如現在服務端資源很緊俏,那麼你就定個時間,過了多久之後就將處於 TIME_WAIT 狀態的對方 ip 和端口告訴這個服務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後這個服務就利用 IP_TRANSPARENT 僞裝成之前的那個 client 向服務端發起一個請求,然後服務端收到會給真的 client 一個 ACK, 那 client 都關了已經,說你在搞啥子,於是回了一個 RST,然後服務端就中止了這個連接。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c4/c4e455b8a83f2d68a45085d9f37aac6b.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":" 超時重傳機制是爲了解決什麼問題?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面我們提到 TCP 要提供可靠的傳輸,那麼網絡又是不穩定的如果傳輸的包對方沒收到卻又得保證可靠那麼就必須重傳。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP 的可靠性是靠確認號的,比如我發給你1、2、3、4這4個包,你告訴我你現在要 5 那說明前面四個包你都收到了,就是這麼回事兒。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不過這裏要注意,SeqNum 和 ACK 都是"},{"type":"text","marks":[{"type":"strong"}],"text":"以字節數爲單位的"},{"type":"text","text":",也就是說假設你收到了1、2、4 但是 3 沒有收到你不能 ACK 5,如果你回了 5 那麼發送方就以爲你5之前的都收到了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以"},{"type":"text","marks":[{"type":"strong"}],"text":"只能回覆確認最大連續收到包"},{"type":"text","text":",也就是 3。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而發送方不清楚 3、4 這兩個包到底是還沒到呢還是已經丟了,於是發送方需要等待,這等待的時間就比較講究了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果太心急可能 ACK 已經在路上了,你這重傳就是浪費資源了,如果太散漫,那麼接收方急死了,這死鬼怎麼還不發包來,我等的花兒都謝了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以這個等待超時重傳的時間很關鍵,怎麼搞?聰明的小夥伴可能一下就想到了,你估摸着正常來回一趟時間是多少不就好了,我就等這麼長。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就來回一趟的時間就叫 RTT,即 Round Trip Time,然後根據這個時間制定超時重傳的時間 RTO,即 Retransmission Timeout。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不過這裏大概只好了 RTO 要參考下 RTT ,但是具體要怎麼算?首先肯定是採樣,然後一波加權平均得到 RTO。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RFC793 定義的公式如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、先採樣 RTT"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、SRTT = ( ALPHA "},{"type":"text","marks":[{"type":"italic"}],"text":" SRTT ) + ((1-ALPHA) "},{"type":"text","text":" RTT)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、RTO = min[UBOUND,max[LBOUND,(BETA*SRTT)]]"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ALPHA 是一個平滑因子取值在 0.8~0.9之間,UBOUND 就是超時時間上界-1分鐘,LBOUND 是下界-1秒鐘,BETA 是一個延遲方差因子,取值在 1.3~2.0。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是還有個問題,RTT 採樣的時間用一開始發送數據的時間到收到 ACK 的時間作爲樣本值還是重傳的時間到 ACK 的時間作爲樣本值?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f9/f9962c711a3916caf0b31363bb654cd0.png","alt":"圖來自網絡","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從圖中就可以看到,一個時間算長了,一個時間算短了,這有點難,因爲你不知道這個 ACK 到底是回覆誰的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以怎麼辦?"},{"type":"text","marks":[{"type":"strong"}],"text":"發生重傳的來回我不採樣不就好了"},{"type":"text","text":",我不知道這次 ACK 到底是回覆誰的,我就不管他,我就採樣正常的來回。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就是 Karn / Partridge 算法,不採樣重傳的RTT。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是不採樣重傳會有問題,比如某一時刻網絡突然就是很差,你要是不管重傳,那麼還是按照正常的 RTT 來算 RTO, 那麼超時的時間就過短了,於是在網絡很差的情況下還瘋狂重傳加重了網絡的負載。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此 Karn 算法就很粗暴的搞了個發生重傳我就將現在的 RTO 翻倍,哼!就是這麼簡單粗暴。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/dc/dc7f7aaa4dcefa68f930200cbc619734.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是這種平均的計算很容易把一個突然間的大波動,平滑掉,所以又搞了個算法,叫 Jacobson / Karels Algorithm。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它把最新的 RTT 和平滑過的 SRTT 做了波計算得到合適的 RTO,公式我就不貼了,反正我不懂,不懂就不嗶嗶了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":" 爲什麼還需要快速重傳機制?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"超時重傳是按時間來驅動的,如果是網絡狀況真的不好的情況,超時重傳沒問題,但是如果網絡狀況好的時候,只是恰巧丟包了,那等這麼長時間就沒必要。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"於是又引入了數據驅動的重傳叫快速重傳,什麼意思呢?就是發送方如果連續三次收到對方相同的確認號,那麼馬上重傳數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲連續收到三次相同 ACK 證明當前網絡狀況是 ok 的,那麼確認是丟包了,於是立馬重發,沒必要等這麼久。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ed/ed71106034aa081facf70e39c9e326d1.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看起來好像挺完美的,但是你有沒有想過我發送1、2、3、4這4個包,就 2 對方沒收到,1、3、4都收到了,然後不管是超時重傳還是快速重傳反正對方就回 ACK 2。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這時候要重傳 2、3、4 呢還是就 2 呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"SACK 的引入是爲了解決什麼問題?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SACK 即 Selective Acknowledgment,它的引入就是爲了解決發送方不知道該重傳哪些數據的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們來看一下下面的圖就知道了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/97/97d3e725093767d7737578801935b799.png","alt":"圖來自網絡","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SACK 就是接收方會回傳它已經接受到的數據,這樣發送方就知道哪一些數據對方已經收到了,所以就可以選擇性的發送丟失的數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如圖,通過 ACK 告知我接下來要 5500 開始的數據,並一直更新 SACK,6000-6500 我收到了,6000-7000的數據我收到了,6000-7500的數據我收到了,發送方很明確的知道,5500-5999 的那一波數據應該是丟了,於是重傳。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而且如果數據是多段不連續的, SACK 也可以發送,比如 SACK 0-500,1000-1500,2000-2500。就表明這幾段已經收到了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"D-SACK 又是什麼東西?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"D-SACK 其實是 SACK 的擴展,它利用 SACK 的第一段來描述重複接受的不連續的數據序號,如果第一段描述的範圍被 ACK 覆蓋,說明重複了,比如我都 ACK 到6000了你還給我回 SACK 5000-5500 呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"說白了就是從第一段的反饋來和已經接受到的 ACK 比一比,參數是 tcp_dsack,Linux 2.4 之後默認開啓。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那知道重複了有什麼用呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、知道重複了說明對方收到剛纔那個包了,所以是回來的 ACK 包丟了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、是不是包亂序的,先發的包後到?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、是不是自己太着急了,RTO 太小了?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4、是不是被數據複製了,搶先一步呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"滑動窗口乾嘛用?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們已經知道了 TCP 有序號,並且還有重傳,但是這還不夠,因爲我們不是愣頭青,還需要根據情況來控制一下發送速率,因爲網絡是複雜多變的,有時候就會阻塞住,而有時候又很通暢。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以發送方需要知道接收方的情況,好控制一下發送的速率,不至於蒙着頭一個勁兒的發然後接受方都接受不過來。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此 TCP 就有個叫滑動窗口的東西來做流量控制,也就是接收方告訴發送方我還能接受多少數據,然後發送方就可以根據這個信息來進行數據的發送。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下是"},{"type":"text","marks":[{"type":"strong"}],"text":"發送方維護的窗口"},{"type":"text","text":",就是黑色圈起來的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1e/1e0d716392fb210ae73513fcae106475.png","alt":"圖來自網絡","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"圖中的 #1 是已收到 ACK 的數據,#2 是已經發出去但是還沒收到 ACK 的數據,#3 就是在窗口內可以發送但是還沒發送的數據。#4 就是還不能發送的數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後此時收到了 36 的 ACK,並且發出了 46-51 的字節,於是窗口向右滑動了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e2/e260ca28b5f46113a541a0c24b127204.png","alt":"圖片來自網絡","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP/IP Guide 上還有一張完整的圖,畫的十分清晰,大家看一下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/34/347621eb39ed5ad361a5d1e8acf09334.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"如果接收方回覆的窗口一直是 0 怎麼辦?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上文已經說了發送方式根據接收方迴應的 window 來控制能發多少數據,如果接收方一直迴應 0,那發送方就杵着?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你想一下,發送方發的數據都得到 ACK 了,但是呢迴應的窗口都是 0 ,這發送方此時不敢發了啊,那也不能一直等着啊,這 Window 啥時候不變 0 啊?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"於是 TCP 有一個 Zero Window Probe 技術,發送方得知窗口是 0 之後,會去探測探測這個接收方到底行不行,也就是發送 ZWP 包給接收方。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具體看實現了,可以發送多次,然後還有間隔時間,多次之後都不行可以直接 RST。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"假設接收方每次迴應窗口都很小怎麼辦?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你想象一下,如果每次接收方都說我還能收 1 個字節,發送方該不該發?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP + IP 頭部就 40 個字節了,這傳輸不划算啊,如果傻傻的一直髮這就叫 Silly Window。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那咋辦,一想就是發送端等着,等養肥了再發,要麼接收端自己自覺點,數據小於一個閾值就告訴發送端窗口此時是 0 算了,也等養肥了再告訴發送端。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"發送端等着的方案就是納格算法,這個算法相信看一下代碼就知道了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d8/d8dc40ba4e4780c9e1f02179774884b2.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簡單的說就是當前能發送的數據和窗口大於等於 MSS 就立即發送,否則再判斷一下之前發送的包 ACK 回來沒,回來再發,不然就攢數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接收端自覺點的方案是 David D Clark’s 方案,如果窗口數據小於某個閾值就告訴發送方窗口 0 別發,等緩過來數據大於等於 MSS 或者接受 buffer 騰出一半空間了再設置正常的 window 值給發送方。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對了提到納格算法不得不再提一下延遲確認,納格算法在等待接收方的確認,而開啓延遲確認則會延遲發送確認,會等之後的包收到了再一起確認或者等待一段時候真的沒了再回復確認。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就相互等待了,然後延遲就很大了,兩個不可同時開啓。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"已經有滑動窗口了爲什麼還要擁塞控制?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面我已經提到了,加了擁塞控制是因爲 TCP 不僅僅就管兩端之間的情況,還需要知曉一下整體的網絡情形,畢竟只有大家都守規矩了道路纔會通暢。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面我們提到了重傳,如果不管網絡整體的情況,肯定就是對方沒給 ACK ,那我就無腦重傳。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果此時網絡狀況很差,所有的連接都這樣無腦重傳,是不是網絡情況就更差了,更加擁堵了?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後越擁堵越重傳,一直衝沖沖!然後就 GG 了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/21/21dde60495ff6a7eb5d8434b21181fa6.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以需要個擁塞控制,來避免這種情況的發送。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"擁塞控制怎麼搞?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主要有以下幾個步驟來搞:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、慢啓動,探探路。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、擁塞避免,感覺差不多了減速看看"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、擁塞發生快速重傳/恢復"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b8/b8c12d896175b20e7e290d3815243a13.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"慢啓動,就是新司機上路慢慢來,初始化 cwnd(Congestion Window)爲 1,然後每收到一個 ACK 就 cwnd++ 並且每過一個 RTT ,cwnd = 2*cwnd 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線性中帶着指數,指數中又夾雜着線性增。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後到了一個閾值,也就是 ssthresh(slow start threshold)的時候就進入了擁塞避免階段。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個階段是每收到一個 ACK 就 cwnd = cwnd + 1/cwnd並且每一個 RTT 就 cwnd++。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到都是線性增。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後就是一直增,直到開始丟包的情況發生,前面已經分析到重傳有兩種,一種是超時重傳,一種是快速重傳。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果發生超時重傳的時候,那說明情況有點糟糕,於是直接把 ssthresh 置爲當前 cwnd 的一半,然後 cwnd 直接變爲 1,進入慢啓動階段。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cc/cc7bc77da284189d50532d63f2e05f0c.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果是快速重傳,那麼這裏有兩種實現,一種是 TCP Tahoe ,和超時重傳一樣的處理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一種是 TCP Reno,這個實現是把 cwnd = cwnd/2 ,然後把 ssthresh 設置爲當前的 cwnd 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後進入快速恢復階段,將 cwnd = cwnd + 3(因爲快速重傳有三次),"},{"type":"text","marks":[{"type":"strong"}],"text":"重傳 DACK 指定的包"},{"type":"text","text":",如果再收到一個DACK則 cwnd++,如果收到是正常的 ACK 那麼就將 cwnd 設爲 ssthresh 大小,進入擁塞避免階段。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到快速恢復就重傳了指定的一個包,那有可能是很多包都丟了,然後其他的包只能等待超時重傳,超時重傳就會導致 cwnd 減半,多次觸發就指數級下降。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以又搞了個 New Reno,多加了個 New,它是在沒有SACK 的情況下改進快速恢復,它會觀察重傳 DACK 指定的包的響應 ACK 是否是已經發送的最大 ACK,比如你發了1、2、3、4,對方沒收到 2,但是 3、4都收到了,於是你重傳 2 之後 ACK 肯定是 5,說明就丟了這一個包。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不然就是還有其他包丟了,如果就丟了一個包就是之前的過程一樣,如果還有其他包丟了就繼續重傳,直到 ACK 是全部的之後再退出快速恢復階段。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簡單的說就是一直探測到全部包都收到了再結束這個環節。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還有個 FACK,它是基於 SACK 用來作爲重傳過程中的擁塞控制,相對於上面的 New Reno 我們就知道它有 SACK 所以不需要一個一個試過去,具體我不展開了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"還有哪些擁塞控制算法?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從維基上看有這麼多。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5f/5fd5cd126c1400de43a7ceb8259c3944.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本來我還想嗶嗶幾句了,嗶嗶了之後又刪了,感覺說了和沒說一樣,"},{"type":"text","marks":[{"type":"strong"}],"text":"想深入但是實力不允許"},{"type":"text","text":",有點惆悵啊。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"各位看官自個兒查查吧,或者等我日後修煉有成再來嗶嗶。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"說了這麼多來總結一下吧。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP 是面向連接的,提供可靠、有序的傳輸並且還提供流控和擁塞控制,單獨提取出 TCP 層而不是在 IP層實現是因爲 IP 層有更多的設備需要使用,加了複雜的邏輯不划算。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"三次握手主要是爲了定義初始序列號爲了之後的傳輸打下基礎,四次揮手是因爲 TCP 是全雙工協議,因此雙方都得說拜拜。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SYN 超時了就階梯性重試,如果有 SYN攻擊,可以加大半隊列數,或減少重試次數,或直接拒絕。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TIME_WAIT 是怕對方沒收到最後一個 ACK,然後又發了 FIN 過來,並且也是等待處理網絡上殘留的數據,怕影響新連接。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/54/5458dd1fe870a82f21a06430b7c396c3.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TIME"},{"type":"text","marks":[{"type":"italic"}],"text":"WAIT 不建議設小,或者破壞 TIME"},{"type":"text","text":"WAIT 機制,如果真想那麼可以開啓快速回收,或者重用,不過注意受益的對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"超時重傳是爲了保證對端一定能收到包,快速重傳是爲了避免在偶爾丟包的時候需要等待超時這麼長時間,SACK 是爲了讓發送方知道重傳哪些。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"D-SACK 是爲了讓發送方知道這次重傳的原因是對方真的沒收到還是自己太心急了 RTO 整小了,不至於兩眼一抹黑。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"滑動窗口是爲了平衡發送方的發送速率和接收方的接受數率,不至於瞎發,當然還需要注意 Silly Window 的情況,同時還要注意納格算法和延遲確認不能一起搭配。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而滑動窗口還不夠,還得有個擁塞控制,因爲"},{"type":"text","marks":[{"type":"strong"}],"text":"出行你我他,安全靠大家"},{"type":"text","text":",TCP 還得跳出來看看關心下當前大局勢。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"最後"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此就差不多了,不過還是有很多很多細節的,TCP 協議太複雜了,這可能是我文章裏面圖畫的最少的一篇了,你看複雜到我圖都畫不來了哈哈哈。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"今天我就說了個皮毛,如有紕漏請趕緊後臺聯繫鞭撻我。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/17/175d47b16d59c1dc2ebed7cf0687fa61.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"巨人的肩膀 "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"http://www.tcpipguide.com/"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"https://www.ionos.com/digitalguide/server/know-how/introduction-to-tcp/"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"https://www.ibm.com/developerworks/cn/linux/l-tcp-sack/"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"https://coolshell.cn/articles/11564.html/"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"https://tools.ietf.org/html/rfc793"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"https://nmap.org/book/tcpip-ref.html"}]},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"我是 yes,從一點點到億點點,我們下篇見"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章