1.5w字 + 24張圖肝翻 TCP

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/33/33ddc97066dfe4a33beb314ca8840235.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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 是一種面向連接的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"單播","attrs":{}}],"attrs":{}},{"type":"text","text":"協議,在 TCP 中,並不存在多播、廣播的這種行爲,因爲 TCP 報文段中能明確發送方和接受方的 IP 地址。","attrs":{}}]},{"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":"在發送數據前,相互通信的雙方(即發送方和接受方)需要建立一條","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"連接","attrs":{}}],"attrs":{}},{"type":"text","text":",在發送數據後,通信雙方需要斷開連接,這就是 TCP 連接的建立和終止。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"TCP 連接的建立和終止","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你看過我之前寫的關於網絡層的一篇文章,你應該知道 TCP 的基本元素有四個:即","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"發送方的 IP 地址、發送方的端口號、接收方的 IP 地址、接收方的端口號","attrs":{}},{"type":"text","text":"。而每一方的 IP + 端口號都可以看作是一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"套接字","attrs":{}}],"attrs":{}},{"type":"text","text":",套接字能夠被唯一標示。套接字就相當於是門,出了這個門,就要進行數據傳輸了。","attrs":{}}]},{"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 的連接建立 -> 終止總共分爲三個階段","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/00/009b4de57c74396f99279096eb388dc6.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"下面我們所討論的重點也是集中在這三個層面。","attrs":{}}]},{"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 連接的建立和關閉過程,其中不包括數據傳輸的部分。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"TCP 建立連接 - 三次握手","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4c/4cb973cab98196e67fa782482e799734.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"服務端進程準備好接收來自外部的 TCP 連接,一般情況下是調用 bind、listen、socket 三個函數完成。這種打開方式被認爲是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"被動打開(passive open)","attrs":{}}],"attrs":{}},{"type":"text","text":"。然後服務端進程處於 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"LISTEN","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態,等待客戶端連接請求。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端通過 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"connect","attrs":{}}],"attrs":{}},{"type":"text","text":" 發起","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"主動打開(active open)","attrs":{}}],"attrs":{}},{"type":"text","text":",向服務器發出連接請求,請求中首部同步位 SYN = 1,同時選擇一個初始序號 sequence ,簡寫 seq = x。SYN 報文段不允許攜帶數據,只消耗一個序號。此時,客戶端進入 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"SYN-SEND","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"服務器收到客戶端連接後,,需要確認客戶端的報文段。在確認報文段中,把 SYN 和 ACK 位都置爲 1 。確認號是 ack = x + 1,同時也爲自己選擇一個初始序號 seq = y。這個報文段也不能攜帶數據,但同樣要消耗掉一個序號。此時,TCP 服務器進入 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"SYN-RECEIVED(同步收到)","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端在收到服務器發出的響應後,還需要給出確認連接。確認連接中的 ACK 置爲 1 ,序號爲 seq = x + 1,確認號爲 ack = y + 1。TCP 規定,這個報文段可以攜帶數據也可以不攜帶數據,如果不攜帶數據,那麼下一個數據報文段的序號仍是 seq = x + 1。這時,客戶端進入 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ESTABLISHED (已連接)","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"服務器收到客戶的確認後,也進入 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ESTABLISHED","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態。","attrs":{}}]}]}]},{"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 個報文段就能夠完成一個 TCP 連接的建立。三次握手的的目的不僅僅在於讓通信雙方知曉正在建立一個連接,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"也在於利用數據包中的選項字段來交換一些特殊信息,交換初始序列號","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"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":"一般首個發送 SYN 報文的一方被認爲是主動打開一個連接,而這一方通常也被稱爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"客戶端","attrs":{}}],"attrs":{}},{"type":"text","text":"。而 SYN 的接收方通常被稱爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"服務端","attrs":{}}],"attrs":{}},{"type":"text","text":",它用於接收這個 SYN,併發送下面的 SYN,因此這種打開方式是被動打開。","attrs":{}}]}],"attrs":{}},{"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 建立一個連接需要三個報文段,釋放一個連接卻需要四個報文段。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"TCP 斷開連接 - 四次揮手","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據傳輸結束後,通信的雙方可以釋放連接。數據傳輸結束後的客戶端主機和服務端主機都處於 ESTABLISHED 狀態,然後進入釋放連接的過程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d7/d72dfc46192a97f9254cff5c78f43ec5.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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 斷開連接需要歷經的過程如下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端應用程序發出釋放連接的報文段,並停止發送數據,主動關閉 TCP 連接。客戶端主機發送釋放連接的報文段,報文段中首部 FIN 位置爲 1 ,不包含數據,序列號位 seq = u,此時客戶端主機進入 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"FIN-WAIT-1(終止等待 1)","attrs":{}}],"attrs":{}},{"type":"text","text":" 階段。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"服務器主機接受到客戶端發出的報文段後,即發出確認應答報文,確認應答報文中 ACK = 1,生成自己的序號位 seq = v,ack = u + 1,然後服務器主機就進入 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CLOSE-WAIT(關閉等待)","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端主機收到服務端主機的確認應答後,即進入 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"FIN-WAIT-2(終止等待2)","attrs":{}}],"attrs":{}},{"type":"text","text":" 的狀態。等待客戶端發出連接釋放的報文段。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"這時服務端主機會發出斷開連接的報文段,報文段中 ACK = 1,序列號 seq = v,ack = u + 1,在發送完斷開請求的報文後,服務端主機就進入了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"LAST-ACK(最後確認)","attrs":{}}],"attrs":{}},{"type":"text","text":"的階段。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端收到服務端的斷開連接請求後,客戶端需要作出響應,客戶端發出斷開連接的報文段,在報文段中,ACK = 1, 序列號 seq = u + 1,因爲客戶端從連接開始斷開後就沒有再發送數據,ack = v + 1,然後進入到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"TIME-WAIT(時間等待)","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態,請注意,這個時候 TCP 連接還沒有釋放。必須經過時間等待的設置,也就是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"2MSL","attrs":{}}],"attrs":{}},{"type":"text","text":" 後,客戶端纔會進入 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CLOSED","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態,時間 MSL 叫做","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"最長報文段壽命(Maximum Segment Lifetime)","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"服務端主要收到了客戶端的斷開連接確認後,就會進入 CLOSED 狀態。因爲服務端結束 TCP 連接時間要比客戶端早,而整個連接斷開過程需要發送四個報文段,因此釋放連接的過程也被稱爲四次揮手。","attrs":{}}]}]}]},{"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 連接的任意一方都可以發起關閉操作,只不過通常情況下發起關閉連接操作一般都是客戶端。然而,一些服務器比如 Web 服務器在對請求作出相應後也會發起關閉連接的操作。TCP 協議規定通過發送一個 FIN 報文來發起關閉操作。","attrs":{}}]},{"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 連接需要四個報文段。TCP 協議還支持一種","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"半開啓(half-open)","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態,雖然這種情況並不多見。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"TCP 半開啓","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP 連接處於半開啓的這種狀態是因爲連接的一方關閉或者終止了這個 TCP 連接卻沒有通知另一方,也就是說兩個人正在微信聊天,cxuan 你下線了你不告訴我,我還在跟你侃八卦呢。此時就認爲這條連接處於","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"半開啓","attrs":{}}],"attrs":{}},{"type":"text","text":"狀態。這種情況發生在通信中的一方處於主機崩潰的情況下,你 xxx 的,我電腦死機了我咋告訴你?只要處於半連接狀態的一方不傳輸數據的話,那麼是無法檢測出來對方主機已經下線的。","attrs":{}}]},{"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":"另外一種處於半開啓狀態的原因是通信的一方關閉了","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"主機電源","attrs":{}},{"type":"text","text":" 而不是正常關機。這種情況下會導致服務器上有很多半開啓的 TCP 連接。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"TCP 半關閉","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然 TCP 支持半開啓操作,那麼我們可以設想 TCP 也支持半關閉操作。同樣的,TCP 半關閉也並不常見。TCP 的半關閉操作是指","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"僅僅關閉數據流的一個傳輸方向","attrs":{}},{"type":"text","text":"。兩個半關閉操作合在一起就能夠關閉整個連接。在一般情況下,通信雙方會通過應用程序互相發送 FIN 報文段來結束連接,但是在 TCP 半關閉的情況下,應用程序會表明","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"自己的想法","attrs":{}},{"type":"text","text":":\"我已經完成了數據的發送發送,併發送了一個 FIN 報文段給對方,但是我依然希望接收來自對方的數據直到它發送一個 FIN 報文段給我\"。 下面是一個 TCP 半關閉的示意圖。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8c/8c869c8204c347fa4109b8453c9b1647.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"解釋一下這個過程:","attrs":{}}]},{"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 報文,要求主動斷開連接,服務器收到 FIN 後,迴應 ACK ,由於此時發起半關閉的一方也就是客戶端仍然希望服務器發送數據,所以服務器會繼續發送數據,一段時間後服務器發送另外一條 FIN 報文,在客戶端收到 FIN 報文迴應 ACK 給服務器後,斷開連接。","attrs":{}}]},{"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 的半關閉操作中,連接的一個方向被關閉,而另一個方向仍在傳輸數據直到它被關閉爲止。只不過很少有應用程序使用這一特性。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"同時打開和同時關閉","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還有一種比較非常規的操作,這就是兩個應用程序同時主動打開連接。雖然這種情況看起來不太可能,但是在特定的安排下卻是有可能發生的。我們主要講述這個過程。","attrs":{}}]},{"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,這個場景還要求通信雙方都知道對方的 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"IP 地址 + 端口號","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"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":"下面是同時打開的例子","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/31/31fe0d8d570efd811ff22c314d17bd32.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"如上圖所示,通信雙方都在收到對方報文前主動發送了 SYN 報文,都在收到彼此的報文後回覆了一個 ACK 報文。","attrs":{}}]},{"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":"一個同時打開過程需要交換四個報文段,比普通的三次握手增加了一個,由於同時打開沒有客戶端和服務器一說,所以這裏我用了通信雙方來稱呼。","attrs":{}}]},{"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 報文,下圖顯示了一個同時關閉的過程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b2/b261df86649fe3d9f836a1c0a80fa063.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"同時關閉過程中需要交換和正常關閉相同數量的報文段,只不過同時關閉不像四次揮手那樣順序進行,而是交叉進行的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"聊一聊初始序列號","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也許是我上面圖示或者文字描述的不專業,初始序列號它是有專業術語表示的,初始序列號的英文名稱是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Initial sequence numbers (ISN)","attrs":{}},{"type":"text","text":",所以我們上面表示的 seq = v,其實就表示的 ISN。","attrs":{}}]},{"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 連接都會有一個不同的初始序列號。RFC 文檔指出初始序列號是一個 32 位的計數器,每 4 us(微秒) + 1。因爲每個 TCP 連接都是一個不同的實例,這麼安排的目的就是爲了防止出現序列號重疊的情況。","attrs":{}}]},{"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 四元組和正確的序列號纔會被對方接收。這也反應了 TCP 報文段容易被","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"僞造","attrs":{}}],"attrs":{}},{"type":"text","text":" 的脆弱性,因爲只要我僞造了一個相同的四元組和初始序列號就能夠僞造 TCP 連接,從而打斷 TCP 的正常連接,所以抵禦這種攻擊的一種方式就是使用初始序列號,另外一種方法就是加密序列號。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"TCP 狀態轉換","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們上面聊到了三次握手和四次揮手,提到了一些關於 TCP 連接之間的狀態轉換,那麼下面我就從頭開始和你好好梳理一下這些狀態之間的轉換。","attrs":{}}]},{"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":"首先第一步,剛開始時服務器和客戶端都處於 CLOSED 狀態,這時需要判斷是主動打開還是被動打開,如果是主動打開,那麼客戶端向服務器發送 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"SYN","attrs":{}}],"attrs":{}},{"type":"text","text":" 報文,此時客戶端處於 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"SYN-SEND","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態,SYN-SEND 表示發送連接請求後等待匹配的連接請求,服務器被動打開會處於 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"LISTEN","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態,用於監聽 SYN 報文。如果客戶端調用了 close 方法或者經過一段時間沒有操作,就會重新變爲 CLOSED 狀態,這一步轉換圖如下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/dd/dde56e4c26b58bf1512f1db3cd3db616.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"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":"這裏有個疑問,爲什麼處於 LISTEN 狀態下的客戶端還會發送 SYN 變爲 SYN_SENT 狀態呢?","attrs":{}}]}],"attrs":{}},{"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":"知乎看到了車小胖大佬的回答,這種情況可能出現在 FTP 中,LISTEN -> SYN_SENT 是因爲這個連接可能是由於服務器端的應用有數據發送給客戶端所觸發的,客戶端被動接受連接,連接建立後,開始傳輸文件。也就是說,處於 LISTEN 狀態的服務器也是有可能發送 SYN 報文的,只不過這種情況非常少見。","attrs":{}}]},{"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_SEND 狀態的服務器會接收 SYN 併發送 SYN 和 ACK 轉換成爲 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"SYN_RCVD","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態,同樣的,處於 LISTEN 狀態的客戶端也會接收 SYN 併發送 SYN 和 ACK 轉換爲 SYN_RCVD 狀態。如果處於 SYN_RCVD 狀態的客戶端收到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"RST","attrs":{}}],"attrs":{}},{"type":"text","text":" 就會變爲 LISTEN 狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bc/bcdd577a83e65ebb9c02e661d1dcab2b.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"這兩張圖一起看會比較好一些。","attrs":{}}]},{"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":"這裏需要解釋下什麼是 RST","attrs":{}}]}],"attrs":{}},{"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 和端口號不匹配的情況。假設客戶端主機發送一個請求,而服務器主機經過 IP 和端口號的判斷後發現不是給這個服務器的,那麼服務器就會發出一個 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"RST","attrs":{}}],"attrs":{}},{"type":"text","text":" 特殊報文段給客戶端。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bb/bbdcaf818de06859b7c848b5a7def788.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"因此,當服務端發送一個 RST 特殊報文段給客戶端的時候,它就會告訴客戶端","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"沒有匹配的套接字連接,請不要再繼續發送了","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"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:(Reset the connection)","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"用於復位因某種原因引起出現的錯誤連接,也用來拒絕非法數據和請求","attrs":{}},{"type":"text","text":"。如果接收到 RST 位時候,通常發生了某些錯誤。","attrs":{}}]},{"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 端口是一種導致 RST 出現的情況,除此之外,RST 還可能由於請求超時、取消一個已存在的連接等出現。","attrs":{}}]},{"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_RCVD 的服務器會接收 ACK 報文,SYN_SEND 的客戶端會接收 SYN 和 ACK 報文,併發送 ACK 報文,由此,客戶端和服務器之間的連接就建立了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a0/a0764c20b958959ecc7b5d9477722aab.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"這裏還要注意一點,同時打開的狀態我在上面沒有刻意表示出來,實際上,在同時打開的情況下,它的狀態變化是這樣的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/19/19f9016d87b3650e1434ffc984d325f0.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"爲什麼會是這樣呢?因爲你想,在同時打開的情況下,兩端主機都發起 SYN 報文,而主動發起 SYN 的主機會處於 SYN-SEND 狀態,發送完成後,會等待接收 SYN 和 ACK , 在雙方主機都發送了 SYN + ACK 後,雙方都處於 SYN-RECEIVED(SYN-RCVD) 狀態,然後等待 SYN + ACK 的報文到達後,雙方就會處於 ESTABLISHED 狀態,開始傳輸數據。","attrs":{}}]},{"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 連接建立過程中的狀態轉換,現在你可以泡一壺茶喝點水,等着數據傳輸了。","attrs":{}}]},{"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 連接就可以斷開了。","attrs":{}}]},{"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_RCVD 狀態的時刻,因爲剛收到了 SYN 包併發送了 SYN + ACK 包,此時服務端很開心,但是這時,服務端應用進程關閉了,然後應用進程發了一個 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"FIN","attrs":{}}],"attrs":{}},{"type":"text","text":" 包,就會讓服務器從 SYN_RCVD -> ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"FIN_WAIT_1","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a4/a4ec9501491a99c24fa1072b3e0f6ac5.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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 報文希望斷開連接,此時客戶端也會變爲 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"FIN_WAIT_1","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態,對於服務器來說,它接收到了 FIN 報文段並回復了 ACK 報文,就會從 ESTABLISHED -> ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CLOSE_WAIT","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f9/f965040c90802edbe023ef89a93a48e9.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"位於 CLOSE_WAIT 狀態的服務端會發送 FIN 報文,然後把自己置於 LAST_ACK 狀態。處於 FIN_WAIT_1 的客戶端接收 ACK 消息就會變爲 FIN_WAIT_2 狀態。","attrs":{}}]},{"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":"這裏需要先解釋一下 CLOSING 這個狀態,FIN_WAIT_1 -> CLOSING 的轉換比較特殊","attrs":{}}]}],"attrs":{}},{"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":"CLOSING 這種狀態比較特殊,實際情況中應該是很少見,屬於一種比較罕見的例外狀態。正常情況下,當你發送FIN 報文後,按理來說是應該先收到(或同時收到)對方的 ACK 報文,再收到對方的 FIN 報文。但是 CLOSING 狀態表示你發送 FIN 報文後,並沒有收到對方的 ACK 報文,反而卻也收到了對方的 FIN 報文。","attrs":{}}]},{"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 報文的情況,也即會出現 CLOSING 狀態,表示雙方都正在關閉連接。","attrs":{}}]},{"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_WAIT_2 狀態的客戶端接收服務端主機發送的 FIN + ACK 消息,併發送 ACK 響應後,會變爲 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"TIME_WAIT","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態。處於 CLOSE_WAIT 的客戶端發送 FIN 會處於 LAST_ACK 狀態。","attrs":{}}]},{"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":"這裏不少圖和博客雖然在圖上畫的是 FIN + ACK 報文後纔會處於 LAST_ACK 狀態,但是描述的時候,一般通常只對於 FIN 進行描述。也就是說 CLOSE_WAIT 發送 FIN 纔會處於 LAST_ACK 狀態。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/89/89ddb828dfa73b8a7a8380c0533b3329.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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_WAIT_1 -> TIME_WAIT 的狀態也就是接收 FIN 和 ACK 併發送 ACK 之後,客戶端處於的狀態。","attrs":{}}]},{"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":"然後位於 CLOSINIG 狀態的客戶端這時候還有 ACK 接收的話,會繼續處於 TIME_WAIT 狀態,可以看到,TIME_WAIT 狀態相當於是客戶端在關閉前的最後一個狀態,它是一種主動關閉的狀態;而 LAST_ACK 是服務端在關閉前的最後一個狀態,它是一種被動打開的狀態。","attrs":{}}]},{"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":"上面有幾個狀態比較特殊,這裏我們向西解釋下。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"TIME_WAIT 狀態","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通信雙方建立 TCP 連接後,主動關閉連接的一方就會進入 TIME_WAIT 狀態。TIME_WAIT 狀態也稱爲 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"2MSL","attrs":{}}],"attrs":{}},{"type":"text","text":" 的等待狀態。在這個狀態下,TCP 將會等待","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"最大段生存期(Maximum Segment Lifetime, MSL)","attrs":{}},{"type":"text","text":" 時間的兩倍。","attrs":{}}]},{"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":"這裏需要解釋下 MSL","attrs":{}}]}],"attrs":{}},{"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 段期望的最大生存時間,也就是在網絡中存在的最長時間。這個時間是有限制的,因爲我們知道 TCP 是依靠 IP 數據段來進行傳輸的,IP 數據報中有 TTL 和跳數的字段,這兩個字段決定了 IP 的生存時間,一般情況下,TCP 的最大生存時間是 2 分鐘,不過這個數值是可以修改的,根據不同操作系統可以修改此值。","attrs":{}}]},{"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 的狀態。","attrs":{}}]},{"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 執行一個主動關閉併發送最終的 ACK 時,TIME_WAIT 應該以 2 * 最大生存時間存在,這樣就能夠讓 TCP 重新發送最終的 ACK 以避免出現丟失的情況。重新發送最終的 ACK 並不是因爲 TCP 重傳了 ACK,而是因爲通信另一方重傳了 FIN,客戶端經常回發送 FIN,因爲它需要 ACK 的響應才能夠關閉連接,如果生存時間超過了 2MSL 的話,客戶端就會發送 RST,使服務端出錯。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"TCP 超時和重傳","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"沒有永遠不出錯誤的通信","attrs":{}},{"type":"text","text":",這句話表明着不管外部條件多麼完備,永遠都會有出錯的可能。所以,在 TCP 的正常通信過程中,也會出現錯誤,這種錯誤可能是由於數據包丟失引起的,也可能是由於數據包重複引起的,甚至可能是由於數據包","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"失序","attrs":{}}],"attrs":{}},{"type":"text","text":" 引起的。","attrs":{}}]},{"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 的接收端返回一系列的確認信息來判斷是否出現錯誤,一旦出現丟包等情況,TCP 就會啓動","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"重傳","attrs":{}}],"attrs":{}},{"type":"text","text":"操作,重傳尚未確認的數據。","attrs":{}}]},{"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 的重傳有兩種方式,一種是基於","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"時間","attrs":{}}],"attrs":{}},{"type":"text","text":",一種是基於","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"確認信息","attrs":{}}],"attrs":{}},{"type":"text","text":",一般通過確認信息要比通過時間更加高效。","attrs":{}}]},{"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 的確認和重傳,都是基於數據包是否被確認爲前提的。","attrs":{}}]}],"attrs":{}},{"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 在發送數據時會設置一個","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"定時器","attrs":{}},{"type":"text","text":",如果在定時器指定的時間內未收到確認信息,那麼就會觸發相應的超時或者基於計時器的重傳操作,計時器超時通常被稱爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"重傳超時(RTO)","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"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":"但是有另外一種不會引起延遲的方式,這就是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"快速重傳","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"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 在每次重傳一次報文後,其重傳時間都會","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"加倍","attrs":{}}],"attrs":{}},{"type":"text","text":",這種\"間隔時間加倍\"被稱爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"二進制指數補償(binary exponential backoff)","attrs":{}},{"type":"text","text":" 。等到間隔時間加倍到 15.5 min 後,客戶端會顯示","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"Connection closed by foreign host.\n","attrs":{}}]},{"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 擁有兩個閾值來決定如何重傳一個報文段,這兩個閾值被定義在 RFC[RCF1122] 中,第一個閾值是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"R1","attrs":{}}],"attrs":{}},{"type":"text","text":",它表示願意嘗試重傳的次數,閾值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"R2","attrs":{}}],"attrs":{}},{"type":"text","text":" 表示 TCP 應該放棄連接的時間。R1 和 R2 應至少設爲三次重傳和 100 秒放棄 TCP 連接。","attrs":{}}]},{"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":"這裏需要注意下,對連接建立報文 SYN 來說,它的 R2 至少應該設置爲 3 分鐘,但是在不同的系統中,R1 和 R2 值的設置方式也不同。","attrs":{}}]}],"attrs":{}},{"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 系統中,R1 和 R2 的值可以通過應用程序來設置,或者是修改 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"net.ipv4.tcp_retries1 和 net.ipv4.tcp_retries2","attrs":{}},{"type":"text","text":" 的值來設置。變量值就是重傳次數。","attrs":{}}]},{"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_retries2 的默認值是 15,這個充實次數的耗時大約是 13 - 30 分鐘,這只是一個大概值,最終耗時時間還要取決於 RTO ,也就是重傳超時時間。tcp_retries1 的默認值是 3 。","attrs":{}}]},{"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 段來說,net.ipv4.tcp_syn_retries 和 net.ipv4.tcp_synack_retries 這兩個值限制了 SYN 的重傳次數,默認是 5,大約是 180 秒。","attrs":{}}]},{"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":"Windows 操作系統下也有 R1 和 R2 變量,它們的值被定義在下方的註冊表中","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"HKLM\\System\\CurrentControlSet\\Services\\Tcpip\\Parameters\nHKLM\\System\\CurrentControlSet\\Services\\Tcpip6\\Parameters\n","attrs":{}}]},{"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":"其中有一個非常重要的變量就是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"TcpMaxDataRetransmissions","attrs":{}}],"attrs":{}},{"type":"text","text":",這個 TcpMaxDataRetransmissions 對應 Linux 中的 tcp_retries2 變量,默認值是 5。這個值的意思表示的是 TCP 在現有連接上未確認數據段的次數。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"快速重傳","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們上面提到了快速重傳,實際上快速重傳機制是基於接收端的反饋信息來觸發的,它並不受重傳計時器的影響。所以與超時重傳相比,快速重傳能夠有效的修復","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"丟包","attrs":{}}],"attrs":{}},{"type":"text","text":"情況。當 TCP 連接的過程中接收端出現亂序的報文(比如 2 - 4 - 3)到達時,TCP 需要","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"立刻","attrs":{}}],"attrs":{}},{"type":"text","text":"生成確認消息,這種確認消息也被稱爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"重複 ACK","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"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 要做到立刻返回,不允許延遲發送,此舉的目的是要告訴發送方某段報文失序到達了,希望發送方指出失序報文段的序列號。","attrs":{}}]},{"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 發送端會等待一定數目的重複 ACK 被接受來決定數據是否丟失並觸發快速重傳。一般這個判斷的數量是 3,這段文字表述可能無法清晰理解,我們舉個例子。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/90/90e527bf34be7b03ad27430358b57130.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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,接收端的期待序號爲 2,當報文段 2 丟失後,報文段 3。失序到達,但是與接收端的期望不匹配,所以接收端會重複發送冗餘 ACK 2。","attrs":{}}]},{"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 後,發送端就知道哪個報文段丟失了,於是發送方會重發這個丟失的報文段,這樣就不用等待重傳定時器的到期,大大提高了效率。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"SACK","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在標準的 TCP 確認機制中,如果發送方發送了 0 - 10000 序號之間的數據,但是接收方只接收到了 0 -1000, 3000 - 10000 之間的數據,而 1000 - 3000 之間的數據沒有到達接收端,此時發送方會重傳 1000 - 10000 之間的數據,實際上這是沒有必要的,因爲 3000 後面的數據已經被接收了。但是發送方無法感知這種情況的存在。","attrs":{}}]},{"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":"如何避免或者說解決這種問題呢?","attrs":{}}]}],"attrs":{}},{"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 報文段中,有一個 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"SACK 選項","attrs":{}},{"type":"text","text":"字段,這個字段是一種**選擇性確認(selective acknowledgment)**機制,這個機制能告訴 TCP 客戶端,用我們的俗語來解釋就是:“我這裏最多允許接收 1000 之後的報文段,但是我卻收到了 3000 - 10000 的報文段,請給我 1000 - 3000 之間的報文段”。","attrs":{}}]},{"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":"但是,這個選擇性確認機制的是否開啓還受一個字段的影響,這個字段就是 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"SACK 允許選項","attrs":{}},{"type":"text","text":"字段,通信雙方在 SYN 段或者 SYN + ACK 段中添加 SACK 允許選項字段來通知對端主機是否支持 SACK,如果雙方都支持的話,後續在 SYN 段中就可以使用 SACK 選項了。","attrs":{}}]},{"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":"這裏需要注意下:SACK 選項字段只能出現在 SYN 段中。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"僞超時和重傳","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在某些情況下,即使沒有出現報文段的丟失也可能會引發報文重傳。這種重傳行爲被稱爲 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"僞重傳(spurious retransmission)","attrs":{}},{"type":"text","text":" ,這種重傳是沒有必要的,造成這種情況的因素可能是由於","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"僞超時(spurious timeout)","attrs":{}},{"type":"text","text":",僞超時的意思就是過早的判定超時發生。造成僞超時的因素有很多,比如報文段失序到達,報文段重複,ACK 丟失等情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/40/4057516670c97dedd5f11269129ed56a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"檢測和處理僞超時的方法有很多,這些方法統稱爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"檢測","attrs":{}}],"attrs":{}},{"type":"text","text":"算法和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"響應","attrs":{}}],"attrs":{}},{"type":"text","text":"算法。檢測算法用於判斷是否出現了超時現象或出現了計時器的重傳現象。一旦出現了超時或者重傳的情況,就會執行響應算法撤銷或者減輕超時帶來的影響,下面是幾種算法,此篇文章暫不深入這些實現細節","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"重複 SACK 擴展- DSACK","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Eifel 檢測算法","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前移 RTO 恢復 - F-RTO","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Eifel 響應算法","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"包失序和包重複","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面我們討論的都是 TCP 如何處理丟包的問題,我們下面來討論一下包失序和包重複的問題。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"包失序","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據包的失序到達是互聯網中極其容易出現的一種情況,由於 IP 層並不能保證數據包的有序性,每個數據包的發送都可能會選擇當前情況傳輸速度最快的鏈路,所以很有可能出現發送了 A - > B -> C 的三個數據包,到達接收端的數據包順序是 C -> A -> B 或者 B -> C -> A 等等。這就是包失序的一種現象。","attrs":{}}]},{"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":"在包傳輸中,主要分爲兩種鏈路:正向鏈路(SYN)和反向鏈路(ACK)","attrs":{}}]}],"attrs":{}},{"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 是無法正確判斷數據包是否丟失的,數據的丟失和失序都會導致接收端收到無序的數據包,造成數據之間的空缺。如果這種空缺不夠大的話,這種情況影響不大;但是如果空缺比較大的話,可能會導致僞重傳。","attrs":{}}]},{"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 的窗口前移,然後收到重複而應該被丟棄的 ACK,導致發送端出現不必要的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"流量突發","attrs":{}},{"type":"text","text":",影響可用網絡帶寬。","attrs":{}}]},{"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 接收端會對接收到的失序報文立刻返回 ACK,所以網絡中任何一個失序到達的報文都可能會造成重複 ACK。假設一旦收到 ACK,就會啓動快速重傳機制,當 ACK 數量激增,就會導致大量不必要的重傳發生,所以快速重傳應該達到","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"重複閾值(dupthresh)","attrs":{}},{"type":"text","text":" 再觸發。但是在互聯網中,嚴重的失序並不常見,因此 dupthresh 的值可以設置的儘量小,一般來說 3 就能處理絕大部分情況。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"包重複","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"包重複也是互聯網中出現很少的一種情況,它指的是在網絡傳輸過程中,包可能會出現傳輸多次的情況,當重傳生成時,TCP 可能會出現混淆。","attrs":{}}]},{"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,這種情況可以使用 SACK 協商來解決。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"TCP 數據流和窗口管理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們在 ","attrs":{}},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s?__biz=MzI0ODk2NDIyMQ==&mid=2247487108&idx=1&sn=7b47f421bb1dee4edb357a10399b7fec&chksm=e999fb96deee7280a17bfff44c27ef11a60e93e48f9da738670a779ecf6accb5a6a4ebd3cbcc&token=771528602&lang=zh_CN#rd","title":"","type":null},"content":[{"type":"text","text":"40 張圖帶你搞懂 TCP 和 UDP","attrs":{}}]},{"type":"text","text":" 這篇文章中知道了可以使用滑動窗口來實現流量控制,也就是說,客戶端和服務器可以相互提供數據流信息的交換,數據流的相關信息主要包括","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"報文段序列號、ACK 號和窗口大小","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0e/0e4d77d3dd265db1ccd99f8181014252.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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 報文段的傳輸方向。可以看到,每個 TCP 報文段中都包括了","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"序列號、ACK 和窗口信息,可能還會有用戶數據","attrs":{}},{"type":"text","text":"。TCP 報文段中的窗口大小表示接收端還能夠接收的緩存空間的大小,以字節爲單位。這個窗口大小是一種動態的,因爲無時無刻都會有報文段的接收和消失,這種動態調整的窗口大小我們稱之爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"滑動窗口","attrs":{}}],"attrs":{}},{"type":"text","text":",下面我們就來具體認識一下滑動窗口。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"滑動窗口","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP 連接的每一端都可以發送數據,但是數據的發送不是沒有限制的,實際上,TCP 連接的兩端都各自維護了一個","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"發送窗口結構 (send window structure)","attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"接收窗口結構 (receive window structure)","attrs":{}},{"type":"text","text":",這兩個窗口結構就是數據發送的限制。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"發送方窗口","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下圖是一個發送方窗口的示例。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/87/879fda3e8fb66956f4648110c233f0c1.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"在這幅圖中,涉及滑動窗口的四種概念:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"已經發送並確認的報文段:發送給接收方後,接收方回回復 ACK 來對報文段進行響應,圖中標註綠色的報文段就是已經經過接收方確認的報文段。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"已經發送但是還沒確認的報文段:圖中綠色區域是經過接收方確認的報文段,而淺藍色這段區域指的是已經發送但是還未經過接收方確認的報文段。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"等待發送的報文段:圖中深藍色區域是等待發送的報文段,它屬於發送窗口結構的一部分,也就是說,發送窗口結構其實是由已發送未確認 + 等待發送的報文段構成。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"窗口滑動時才能發送的報文段:如果圖中的 [4,9] 這個集合內的報文段發送完畢後,整個滑動窗口會向右移動,圖中橙色區域就是窗口右移時才能發送的報文段。","attrs":{}}]}]}],"attrs":{}},{"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":"滑動窗口也是有邊界的,這個邊界是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Left edge","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Right edge","attrs":{}}],"attrs":{}},{"type":"text","text":",Left edge 是窗口的左邊界,Right edge 是窗口的右邊界。","attrs":{}}]},{"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":"當 Left edge 向右移動而 Right edge 不變時,這個窗口可能處於 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"close","attrs":{}}],"attrs":{}},{"type":"text","text":" 關閉狀態。隨着已發送的數據逐漸被確認從而導致窗口變小時,就會發生這種情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d5/d59b6b99d754ffdf56270f79b25aec2c.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"當 Right edge 向右移動時,窗口會處於 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"open","attrs":{}}],"attrs":{}},{"type":"text","text":" 打開狀態,允許發送更多的數據。當接收端進程讀取緩衝區數據,從而使緩衝區接收更多數據時,就會處於這種狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a3/a3664528c6f35931ef4f04be6cb3ff68.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"還可能會發生 Right edge 向左移動的情況,會導致發送並確認的報文段變小,這種情況被稱爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"糊塗窗口綜合症","attrs":{}},{"type":"text","text":",這種情況是我們不願意看到的。出現糊塗窗口綜合症時,通信雙方用於交換的數據段大小會變小,而網絡固定的開銷卻沒有變化,每個報文段中有用數據相對於頭部信息的比例較小,導致傳輸效率非常低。","attrs":{}}]},{"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":"這就相當於之前你明明有能力花一天時間寫完一個複雜的頁面,現在你花了一天的時間卻改了一個標題的 bug,大材小用。","attrs":{}}]},{"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 報文段都包含","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"ACK 號和窗口通告信息","attrs":{}},{"type":"text","text":",所以每當收到響應時,TCP 接收方都會根據這兩個參數調整窗口結構。","attrs":{}}]},{"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 滑動窗口的 Left edge 永遠不可能向左移動,因爲發送並確認的報文段永遠不可能被取消,就像這世界上沒有後悔藥一樣。這條邊緣是由另一段發送的 ACK 號控制的。當 ACK 標號使窗口向右移動但是窗口大小沒有改變時,則稱該窗口","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"向前滑動","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"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 的編號增加但是窗口通告信息隨着其他 ACK 的到達卻變小了,此時 Left edge 會接近 Right edge。當 Left edge 和 Right edge 重合時,此時發送方不會再傳輸任何數據,這種情況被稱爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"零窗口","attrs":{}}],"attrs":{}},{"type":"text","text":"。此時 TCP 發送方會發起","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"窗口探測","attrs":{}}],"attrs":{}},{"type":"text","text":",等待合適的時機再發送數據。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"接收方窗口","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接收方也維護了一個窗口結構,這個窗口要比發送方的簡單很多。這個窗口記錄了已經接收並確認的數據,以及它能夠接收的最大序列號。接收方的窗口結構不會存儲重複的報文段和 ACK,同時接收方的窗口也不會記錄不應該收到的報文段和 ACK。下面是 TCP 接收方的窗口結構。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/39/3915fe1a35514cba26d8f1cac4401402.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"與發送端的窗口一樣,接收方窗口結構也維護了一個 Left edge 和 Right edge。位於 Left edge 左邊的被稱爲已經接收並確認的報文段,位於 Right edge 右邊的被稱爲不能接收的報文段。","attrs":{}}]},{"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":"對於接收端來說,到達序列號小於 Left efge 的被認爲是已經重複的數據,需要丟棄。超過 Right edge 的被認爲超出處理範圍。只有當到達的報文段等於 Left edge 時,數據纔不會被丟棄,窗口才能夠向前滑動。","attrs":{}}]},{"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 緩衝區溢出,通告發送方不要再發送數據了,但是應用進程卻以非常慢的速度消耗緩衝區的數據(比如 1 字節),就會告訴接收端只能發送一個字節的數據,這個過程慢慢持續,造成網絡開銷大,效率很低。","attrs":{}}]},{"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":"我們上面提到了窗口存在 Left edge = Right edge 的情況,此時被稱爲零窗口,下面我們就來具體研究一下零窗口。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"零窗口","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP 是通過接收端的窗口通告信息來實現流量控制的。通告窗口告訴了 TCP ,接收端能夠接收的數據量。當接收方的窗口變爲 0 時,可以有效的阻止發送端繼續發送數據。當接收端重新獲得可用空間時,它會給發送端傳輸一個 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"窗口更新","attrs":{}}],"attrs":{}},{"type":"text","text":" 告知自己能夠接收數據了。窗口更新一般是純 ACK ,即不帶任何數據。但是純 ACK 不能保證一定會到達發送端,於是需要有相關的措施能夠處理這種丟包。","attrs":{}}]},{"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 丟失的話,通信雙方就會一直處於等待狀態,發送方心想拉垮的接收端怎麼還讓我發送數據!接收端心想天殺的發送方怎麼還不發數據!爲了防止這種情況,發送方會採用一個","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"持續計時器","attrs":{}},{"type":"text","text":"來間歇性的查詢接收方,看看其窗口是否已經增長。持續計時器會觸發","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"窗口探測","attrs":{}}],"attrs":{}},{"type":"text","text":",強制要求接收方返回帶有更新窗口的 ACK。","attrs":{}}]},{"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 持續計時器超時後,就會觸發窗口探測的發送。一個字節的數據能否被接收端接收,還要取決於其緩衝區的大小。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"擁塞控制","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了 TCP 的窗口控制後,使計算機網絡中兩個主機之間不再是以單個數據段的形式發送了,而是能夠連續發送大量的數據包。然而,大量數據包同時也伴隨着其他問題,比如網絡負載、網絡擁堵等問題。TCP 爲了防止這類問題的出現,使用了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"擁塞控制","attrs":{}}],"attrs":{}},{"type":"text","text":" 機制,擁塞控制機制會在面臨網絡擁塞時遏制發送方的數據發送。","attrs":{}}]},{"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":"擁塞控制主要有兩種方法","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"端到端的擁塞控制","attrs":{}}],"attrs":{}},{"type":"text","text":": 因爲網絡層沒有爲運輸層擁塞控制提供顯示支持。所以即使網絡中存在擁塞情況,端系統也要通過對網絡行爲的觀察來推斷。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"TCP 就是使用了端到端的擁塞控制方式","attrs":{}},{"type":"text","text":"。IP 層不會向端系統提供有關網絡擁塞的反饋信息。那麼 TCP 如何推斷網絡擁塞呢?","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果超時或者三次冗餘確認就被認爲是網絡擁塞,TCP 會減小窗口的大小,或者增加往返時延來避免","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"網絡輔助的擁塞控制","attrs":{}}],"attrs":{}},{"type":"text","text":": 在網絡輔助的擁塞控制中,路由器會向發送方提供關於網絡中擁塞狀態的反饋。這種反饋信息就是一個比特信息,它指示鏈路中的擁塞情況。","attrs":{}}]}]}],"attrs":{}},{"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":"下圖描述了這兩種擁塞控制方式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/41/416bf211bbcd12ab9f88478bafa0296c.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"TCP 擁塞控制","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你看到這裏,那我就暫定認爲你瞭解了 TCP 實現可靠性的基礎了,那就是使用序號和確認號。除此之外,另外一個實現 TCP 可靠性基礎的就是 TCP 的擁塞控制。如果說","attrs":{}}]},{"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 發送方感知到沒有什麼擁塞,則 TCP 發送方會增加發送速率;如果發送方感知沿着路徑有阻塞,那麼發送方就會降低發送速率。","attrs":{}}]},{"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":"但是這種方法有三個問題","attrs":{}}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"TCP 發送方如何限制它向其他連接發送報文段的速率呢?","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"一個 TCP 發送方是如何感知到網絡擁塞的呢?","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"當發送方感知到端到端的擁塞時,採用何種算法來改變其發送速率呢?","attrs":{}}]}]}]}],"attrs":{}},{"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":"我們先來探討一下第一個問題,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"TCP 發送方如何限制它向其他連接發送報文段的速率呢","attrs":{}},{"type":"text","text":"?","attrs":{}}]},{"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 是由接收緩存、發送緩存和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"變量(LastByteRead, rwnd,等)","attrs":{}}],"attrs":{}},{"type":"text","text":"組成。發送方的 TCP 擁塞控制機制會跟蹤一個變量,即 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"擁塞窗口(congestion window)","attrs":{}}],"attrs":{}},{"type":"text","text":" 的變量,擁塞窗口表示爲 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cwnd","attrs":{}}],"attrs":{}},{"type":"text","text":",用於限制 TCP 在接收到 ACK 之前可以發送到網絡的數據量。而","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"接收窗口(rwnd)","attrs":{}}],"attrs":{}},{"type":"text","text":" 是一個用於告訴接收方能夠接受的數據量。","attrs":{}}]},{"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 和 rwnd 的最小值,也就是","attrs":{}}]},{"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":"LastByteSent - LastByteAcked <= min(cwnd,rwnd)","attrs":{}}]},{"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,我們假設接收端有足夠的緩存空間用於接收數據,我們就不用考慮 rwnd 了,只專注於 cwnd,那麼,該發送方的發送速率大概是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cwnd/RTT 字節/秒","attrs":{}}],"attrs":{}},{"type":"text","text":" 。通過調節 cwnd,發送方因此能調整它向連接發送數據的速率。","attrs":{}}]},{"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","attrs":{}}],"text":"一個 TCP 發送方是如何感知到網絡擁塞的呢","attrs":{}},{"type":"text","text":"?","attrs":{}}]},{"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 根據超時或者 3 個冗餘 ACK 來感知的。","attrs":{}}]},{"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","attrs":{}}],"text":"當發送方感知到端到端的擁塞時,採用何種算法來改變其發送速率呢","attrs":{}},{"type":"text","text":" ?","attrs":{}}]},{"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 會遵循下面這幾種指導性原則","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果在報文段發送過程中丟失,那就意味着網絡擁堵,此時需要適當降低 TCP 發送方的速率。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個確認報文段指示發送方正在向接收方傳遞報文段,因此,當對先前未確認報文段的確認到達時,能夠增加發送方的速率。爲啥呢?因爲未確認的報文段到達接收方也就表示着網絡不擁堵,能夠順利到達,因此發送方擁塞窗口長度會變大,所以發送速率會變快","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"帶寬探測","attrs":{}}],"attrs":{}},{"type":"text","text":",帶寬探測說的是 TCP 可以通過調節傳輸速率來增加/減小 ACK 到達的次數,如果出現丟包事件,就會減小傳輸速率。因此,爲了探測擁塞開始出現的頻率, TCP 發送方應該增加它的傳輸速率。然後慢慢使傳輸速率降低,進而再次開始探測,看看擁塞開始速率是否發生了變化。","attrs":{}}]}]}],"attrs":{}},{"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 的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"擁塞控制算法(TCP congestion control algorithm)","attrs":{}}],"attrs":{}},{"type":"text","text":" 了。TCP 擁塞控制算法主要包含三個部分:慢啓動、擁塞避免、快速恢復,下面我們依次來看一下","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"慢啓動","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當一條 TCP 開始建立連接時,cwnd 的值就會初始化爲一個 MSS 的較小值。這就使得初始發送速率大概是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"MSS/RTT 字節/秒","attrs":{}}],"attrs":{}},{"type":"text","text":" ,比如要傳輸 1000 字節的數據,RTT 爲 200 ms ,那麼得到的初始發送速率大概是 40 kb/s 。實際情況下可用帶寬要比這個 MSS/RTT 大得多,因此 TCP 想要找到最佳的發送速率,可以通過 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"慢啓動(slow-start)","attrs":{}}],"attrs":{}},{"type":"text","text":" 的方式,在慢啓動的方式中,cwnd 的值會初始化爲 1 個 MSS,並且每次傳輸報文確認後就會增加一個 MSS,cwnd 的值會變爲 2 個 MSS,這兩個報文段都傳輸成功後每個報文段 + 1,會變爲 4 個 MSS,依此類推,每成功一次 cwnd 的值就會翻倍。如下圖所示","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/56/5695195113ea10bd0f1b90a29dfd4897.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"發送速率不可能會一直增長,增長總有結束的時候,那麼何時結束呢?慢啓動通常會使用下面這幾種方式結束髮送速率的增長。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果在慢啓動的發送過程出現丟包的情況,那麼 TCP 會將發送方的 cwnd 設置爲 1 並重新開始慢啓動的過程,此時會引入一個 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ssthresh(慢啓動閾值)","attrs":{}}],"attrs":{}},{"type":"text","text":" 的概念,它的初始值就是產生丟包的 cwnd 的值 / 2,即當檢測到擁塞時,ssthresh 的值就是窗口值的一半。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二種方式是直接和 ssthresh 的值相關聯,因爲當檢測到擁塞時,ssthresh 的值就是窗口值的一半,那麼當 cwnd > ssthresh 時,每次翻番都可能會出現丟包,所以最好的方式就是 cwnd 的值 = ssthresh ,這樣 TCP 就會轉爲擁塞控制模式,結束慢啓動。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"慢啓動結束的最後一種方式就是如果檢測到 3 個冗餘 ACK,TCP 就會執行一種快速重傳並進入恢復狀態。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"擁塞避免","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當 TCP 進入擁塞控制狀態後,cwnd 的值就等於擁塞時值的一半,也就是 ssthresh 的值。所以,無法每次報文段到達後都將 cwnd 的值再翻倍。而是採用了一種相對","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"保守","attrs":{}}],"attrs":{}},{"type":"text","text":"的方式,每次傳輸完成後只將 cwnd 的值增加","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"一個 MSS","attrs":{}}],"attrs":{}},{"type":"text","text":",比如收到了 10 個報文段的確認,但是 cwnd 的值只增加一個 MSS。這是一種線性增長模式,它也會有增長逾值,它的增長逾值和慢啓動一樣,如果出現丟包,那麼 cwnd 的值就是一個 MSS,ssthresh 的值就等於 cwnd 的一半;或者是收到 3 個冗餘的 ACK 響應也能停止 MSS 增長。如果 TCP 將 cwnd 的值減半後,仍然會收到 3 個冗餘 ACK,那麼就會將 ssthresh 的值記錄爲 cwnd 值的一半,進入 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"快速恢復","attrs":{}}],"attrs":{}},{"type":"text","text":" 狀態。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"快速恢復","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在快速恢復中,對於使 TCP 進入快速恢復狀態缺失的報文段,對於每個收到的冗餘 ACK,cwnd 的值都會增加一個 MSS 。當對丟失報文段的一個 ACK 到達時,TCP 在降低 cwnd 後進入擁塞避免狀態。如果在擁塞控制狀態後出現超時,那麼就會遷移到慢啓動狀態,cwnd 的值被設置爲 1 個 MSS,ssthresh 的值設置爲 cwnd 的一半。","attrs":{}}]},{"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","attrs":{}}],"text":"我自己肝了六本 PDF,全網傳播超過10w+ ,微信搜索「程序員cxuan」關注公衆號後,在後臺回覆 cxuan ,領取全部 PDF,這些 PDF 如下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://s3.ax1x.com/2020/11/30/DgOK6f.png","title":"","type":null},"content":[{"type":"text","text":"六本 PDF 鏈接","attrs":{}}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章