【TCP/IP】TCP協議的流程圖解

一、TCP協議起步

1. 什麼是TCP協議

TCP是面向連接的協議,這是因爲在一個應用進程可以開始向另一個應用進程發送數據之前,這兩個進程必須先相互“握手”,即它們必須相互發送某些預備報文段,以建立確保數據傳輸的參數。它有以下幾個特點:

  • 面向連接:TCP一定是“一對一”的,無法像 UDP 協議那樣在同一時刻像多個主機發送消息,即無法做到一對多;
  • 可靠的:無論的網絡鏈路中出現了怎樣的鏈路變化,TCP 都可以保證一個報文一定能夠到達接收端(當然不是說絕對可靠);
  • 基於字節流:消息是“沒有邊界”的,所以無論我們消息有多大都可以進行傳輸。並且消息是“有序的”,當前一個消息沒有收到的時候,即使它先收到了後面的字節已經收到,那麼也不能扔給應用層去處理,同時對重複的報文會自動丟棄。

2. 爲什麼需要TCP協議,它在哪一層工作?

IP 層是“不可靠”的,它不保證網絡包的交付、不保證網絡包的按序交付、也不保證網絡包中數據的完整性。如果需要保障網絡數據包的可靠性,那麼就需要由上層(傳輸層)的 TCP 協議來負責。因爲 TCP 是一個工作在傳輸層可靠數據傳輸的服務,它能確保接收端接收的網絡包是無損壞、無間隔、非冗餘和按序的。

二、認識TCP頭部格式

  • 【端口號】:端口號,用於多路複用或者分解來自(送到)上層的數據;
  • 【序列號】:在連接建立時由計算機計算出的初始值,通過 SYN 包傳給對端主機,每發送一次新的數據包,就累加一次該序列號的大小。用來解決網絡包亂序問題
  • 【確認應答號】:指下次期望收到的數據的序列號,發送端收到這個確認應答以後可以確認確認應答號-1的數據包已經被正常接收。主要用來解決不丟包的問
  • 【標誌字段】:
    • 【ACK】:用以指示確認字段中的值是有效的,即該報文段包括一個對已被成功接收的報文段的確認;
    • 【RST】:用以指示連接的強制拆除,當接收到錯誤連接時會發送RST位置爲1的報文;
    • 【SYN】:用以指示連接的建立,該位爲1的報文表示希望建立連接;
    • 【FIN】:用以指示連接的終止,該位爲1的報文表示希望斷開連接;

三、TCP建立連接(三次握手)

1. TCP三次握手的流程

  • 假設一開始客戶端和服務端都處於CLOSED的狀態。然後先是服務端主動監聽某個端口,處於LISTEN狀態;

  • 【第一個報文】:客戶端會隨機初始化序列號(client_isn),將此序列號置於TCP首部的序列號字段中,同時將SYN標誌位置爲1,表示該報文爲SYN報文。接下來就將第一個SYN報文發送給服務端,表示向服務端發起連接,該報文不包含應用層數據,之後客戶端處於 SYN-SENT 狀態。

  • 【第二個報文】:服務端收到客戶端的SYN報文後,首先服務端也隨機初始化自己的序列號(server_isn),將此序號填入TCP首部的序號字段中,其次把TCP首部的確認應答號字段填入 client_isn + 1,接着把 SYN 和 ACK 標誌位置爲1。最後把該報文發給客戶端,該報文也不包含應用層數據,之後服務端處於 SYN-RCVD 狀態。

  • 【第三個報文】:客戶端收到服務端報文後,還要向服務端迴應最後一個報文,首先應答報文 TCP 首部 ACK 標誌位置爲1,其次確認應答號字段填入 server_isn + 1,最後把報文發送給服務端,這次報文可以攜帶客戶端到服務器的數據,之後客戶端處於 ESTABLISHED 狀態。

  • 最後服務器收到客戶端的應答報文後,也進入 ESTABLISHED 狀態。

2. 爲什麼是三次握手

爲什麼TCP連接確立需要三次握手,而不是兩次?還是四次?這是一個經常能被問到的問題。接下來就幾個方面分析爲什麼需要三次握手的原因:

2.1 避免歷史連接(主要原因)

The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.

上面文字出自 RFC 793,三次握手的主要原因是爲了防止舊的重複連接初始化造成混亂。

網絡環境是比較複雜的,它不一定能保證我們先發送的數據包就一定能再我們期望的時間內送達,它可能半路 Poor Gay 了。也有可能超時後再抵達服務端,那麼這時的TCP就會產生以下的一種情況:

如上圖所示,如果一個SYN報文再超時後沒有得到響應,客戶端可能再次發送一個新的SYN請求,而這時舊的SYN請求可能比新的SYN請求先達到服務器。如果此時沒有第三次連接來確認此次連接是否是歷史連接的話,那麼雙方可能會建立兩個鏈接?造成數據混亂。而如果是三次連接的話,客戶端就有機會再去確認或者中止掉錯誤的連接,防止歷史連接初始化了連接

2.2 同步雙方初始序列號

TCP協議通信的可靠性,是靠着“序列號”所維持的,上面我們看到,每次通信後,都會回一個ACK報文,ACK中的確認應答號都是靠着之前報文的序列號 + 1實現的,這樣做有幾個好處:

  • 接收方可以去除重複的數據報;
  • 接收方可以根據數據包的序列號按序接收;
  • 可以標識發送出去的數據包中, 哪些是已經被對方收到的;

可見,序列號在 TCP 連接中佔據着非常重要的作用,所以當客戶端發送攜帶初始序列號的 SYN 報文的時候,需要服務端回一個 ACK 應答報文,表示客戶端的 SYN 報文已被服務端成功接收,那當服務端發送初始序列號給客戶端的時候,依然也要得到客戶端的應答迴應,這樣一來一回,才能確保雙方的初始序列號能被可靠的同步。

四次握手其實也能夠可靠的同步雙方的初始化序號,但由於第二步和第三步可以優化成一步,所以就成了“三次握手”。

而兩次握手只保證了一方的初始序列號能被對方成功接收,沒辦法保證雙方的初始序列號都能被確認接收。

2.3 避免資源浪費

上面我們在介紹避免歷史連接的時候,就已經提及到了如果沒有三次握手,無法確認客戶端收到了服務端發送的建立連接的 ACK 確認序號信號。

假如我們現在是兩次握手,如果有網絡阻塞等原因造成舊的連接SYN請求還沒抵達服務端,就已經達到了超時時間,那麼客戶端就會再次發送請求SYN報文,之後如果兩次請求都能達到服務端的話,由於服務端現在只有兩次握手,無法確定當前的SYN就是客戶端想要的連接,只能一收到 SYN 就返回一個 ACK 報文再建立起一個連接,這麼做就會造成資源的浪費。看下圖所示:

小結,不能使用兩次握手四次握手的原因:

兩次握手:無法防止歷史連接的建立,會造成雙方資源的浪費,也無法可靠的同步雙方序列號;

四次握手:三次握手就已經理論上最少可靠連接建立,所以不需要使用更多的通信次數。

四、TCP傳輸過程的控制

1. 如何提高數據傳輸的可靠性

從上面我們聊TCP連接的建立過程,我們可以知道,當客戶端的數據達到接收主機的時候,服務端主機會返回一個已收到消息的通知。這個消息就是前面提到的應答消息(ACK)。這個消息機制具體的實現就是,每次當接收端收到對端發送過來的消息時,都會將對端消息中的序列號+1,作爲自己消息發送的應答號。

TCP通過肯定的確認應答(ACK)實現可靠的數據傳輸。當發送端將數據發送之後等待對端的確認應答。如果有確認應答,說明數據已經成功到達對端。反之,則數據丟失的可能性很大。當在一定時間內沒有收到確認應答,發送端就認爲數據已經丟失,那麼就會進行重新發送。由此,即使產生了丟包,仍然能夠保證數據能夠到達對端,實現可靠傳輸。這個過程如下所示:

當然還有另外一種情況,就是主機B已經收到了數據,丟失確認應答的消息在傳輸過程中丟失,那麼此時主機A在一段時間內沒有收到確認應答消息,也會認爲主機B沒有收到消息,從而再發送一次,如下圖:

這種情況在傳輸並不鮮見,如果一直收到主機A發送的重複數據,對於主機B來說,它必須去放棄一些重複的包,這就需要我們上面所提到的序列號了。根據序列號判斷這個數據包先前是否收到過,如果收到過就放棄,如果沒有收到就保留。序列號的生成有其獨特算法,這裏就不做贅述了。

2. 超時重發如何確定

超時重傳機制是用來確保TCP傳輸的可靠性的重要手段之一,我們在上面已經提及過多次:每次發送數據包時,發送的數據報都有seq號,接收端收到數據後,會回覆 ACK 進行確認,表示某 seq 號數據已經收到。發送端在發送了某個seq包後,等待一段時間,如果沒有收到對應的 ACK 回覆,就認爲該報文丟失,會重傳這個數據包。中間等待的這段時間我們稱爲超時時間。那麼如何來確定這個超時時間呢?

比較理想的方式就是定義一個固定值的最小時間,它能保證“確認應答一定能在這個時間內返回”。但是這樣卻有很大的弊端,因爲在長距離的通信時(訪問外網)時,延遲肯定就大,那麼如果把值設置得太大,那麼短距離的通信就很不友好了(隔了那麼就才能知道這個包丟了)。如果值設置得小了,那麼每次長距離訪問都被判定爲丟包???所以這樣設置固定值是不可取的,需要根據網絡的延遲,動態設置超時時間

在這裏先引入兩個概念:

  • RTT(Round Trip Time):往返時延,也就是數據包從發出去到收到對應 ACK 的時間。RTT 是針對連接的,每一個連接都有各自獨立的 RTT。
  • RTO(Retransmission Time Out):重傳超時,也就是前面說的超時時間。

TCP經典算法RTT是:R <- αR + (1-α)M,重傳時間爲: RTO=Rβ

// 挖坑,以後再寫,這裏要寫的話太長了。

此外,數據也不會被無限次、反覆地發送。達到一定重發次數後,如果仍然沒有任何確認應答返回,就會判斷爲網絡或者對端主機發生了異常,強制關閉連接。並且通知應用通信異常強行終止。

3. 利用窗口控制提高速度

通過上面我們知道,TCP通過確認機制來保證數據傳輸的可靠性,在比較早的時候使用的是send-wait-send的方式,也稱爲stop-wait的方式,就是我們上面提及的方式。發送數據的一方在發送數據的時候會啓動定時器,但是如果數據或者ACK丟失,那麼定時器到期之後,收不到ACK就認爲發送出現狀況,要進行重傳。這樣就會降低了通信的效率,如下圖所示,這種方式被稱爲 positive acknowledgment with retransmission (PAR)

由此我們引入了滑動窗口的概念,籠統講就是我每次發送的包都有一個序列號,接收端必須對每一個包進行確認,這樣主機 A一次可以多發送幾個包,而不必等待ACK後再發送,同時接收端也要告知發送端它能夠收多少個包,這樣發送端發起來也有個限制。當然還需要保證順序性,不要亂序,對於亂序的狀況,我們可以允許等待一定情況下的亂序,比如先緩存提前到的數據,然後去等待需要的數據,如果一定時間沒來就DROP掉,來保證順序性。

3.1 數據類別

在TCP協議中,滑動窗口的引入解決了上述問題,接下來我們進一步瞭解滑動窗口的相關概念,首先發送端數據分爲以下幾個類別:

  • Sent and Acknowledged:這些數據表示已經發送成功並已經被確認的數據,比如圖中的前31個bytes,這些數據其實的位置是在窗口之外了,因爲窗口內順序最低的被確認之後,要移除窗口,實際上是窗口進行合攏,同時打開接收新的帶發送的數據

  • Send But Not Yet Acknowledged:這部分數據稱爲發送但沒有被確認,數據被髮送出去,沒有收到接收端的ACK,認爲並沒有完成發送,這個屬於窗口內的數據。

  • Not Sent,Recipient Ready to Receive:這部分是儘快發送的數據,這部分數據已經被加載到緩存中,也就是窗口中了,等待發送,其實這個窗口是完全有接收方告知的,接收方告知還是能夠接受這些包,所以發送方需要儘快的發送這些包

  • Not Sent,Recipient Not Ready to Receive: 這些數據屬於未發送,同時接收端也不允許發送的,因爲這些數據已經超出了發送端所接收的範圍。

對於接收端也是有一個接收窗口的,類似發送端,接收端的數據有3個分類,因爲接收端並不需要等待ACK所以它沒有類似的接收並確認了的分類,情況如下:

  • Received and ACK Not Send to Process:這部分數據屬於接收了數據但是還沒有被上層的應用程序接收,也是被緩存在窗口內

  • Received Not ACK:已經接收並,但是還沒有回覆ACK,這些包可能輸屬於Delay ACK的範疇了

  • Not Received:有空位,還沒有被接收的數據。

3.2 發送窗口與可用窗口

對於發送方來講,窗口內的包括兩部分,就是發送窗口(已經發送了,但是沒有收到ACK),可用窗口,接收端允許發送但是沒有發送的那部分稱爲可用窗口。

  • Send Window: 20 個bytes 這部分值是有接收方在三次握手的時候進行通告的,同時在接收過程中也不斷的通告可以發送的窗口大小,來進行適應;

  • Window Already Sent: 已經發送的數據,但是並沒有收到ACK。

3.3 滑動窗口原理

TCP並不是每一個報文段都會回覆ACK的,可能會對兩個或者多個報文段回覆一個ACK【累計ACK】。比如說發送端有1、2、3 共計3個報文段,先發送了2,3兩個報文段,但是接收方還期望收到1報文段,這個時候2,3報文段就只能放在緩存中等待報文1的空洞被填上,如果報文1,一直不來,報文2,3也將被丟棄,如果報文1來了,那麼會發送一個ACK對這3個報文進行一次確認。接下來舉一個例子來說明滑動窗口的原理:

舉一個例子來說明一下滑動窗口的原理:

  1. 假設32~45 這些數據,是上層 Application 發送給 TCP 的,TCP將其分成四個 Segment 來發往 Internet;

  2. seg1 32~34, seg2 35~36, seg3 37~41,seg4 42~45 這四個片段,依次發送出去,此時假設接收端之接收到了seg1 seg2 seg4;

  3. 此時接收端的行爲是回覆一個ACK包說明已經接收到了32~36的數據,並將 seg4 進行緩存(保證順序,產生一個保存 seg3 的 hole);

  4. 發送端收到ACK之後,就會將32~36的數據包從發送並沒有確認切到發送已經確認,提出窗口,這個時候窗口向右移動

  5. 假設接收端通告的Window Size仍然不變,此時窗口右移,產生一些新的空位,這些是接收端允許發送的範疇;

  6. 對於丟失的seg3,如果超過一定時間,TCP就會重新傳送(重傳機制),重傳成功會seg3,seg4一塊被確認,不成功,seg4也將被丟棄;

就是不斷重複着上述的過程,隨着窗口不斷滑動,將真個數據流發送到接收端,實際上接收端的 Window Size 通告也是會變化的,接收端根據這個值來確定何時及發送多少數據,從對數據流進行流控。原理圖如下圖所示:

3.4 滑動窗口動態調整

主要是根據接收端的接收情況,動態去調整Window Size,然後來控制發送端的數據流量

  1. 客戶端不斷快速發送數據,服務器接收相對較慢,看下實驗的結果;
  2. 包175,發送ACK攜帶WIN = 384,告知客戶端,現在只能接收384個字節;
  3. 包176,客戶端果真只發送了384個字節,Wireshark 也比較智能,也宣告TCP Window Full;
  4. 包177,服務器回覆一個ACK,並通告窗口爲0,說明接收方已經收到所有數據,並保存到緩衝區,但是這個時候應用程序並沒有接收這些數據,導致緩衝區沒有更多的空間,故通告窗口爲0, 這也就是所謂的零窗口,零窗口期間,發送方停止發送數據;
  5. 客戶端察覺到窗口爲0,則不再發送數據給接收方;
  6. 包178,接收方發送一個窗口通告,告知發送方已經有接收數據的能力了,可以發送數據包了;
  7. 包179,收到窗口通告之後,就發送緩衝區內的數據了。

總結一點,就是接收端可以根據自己的狀況通告窗口大小,從而控制發送端的接收,進行流量控制。

五、TCP終止連接(四次揮手)

瞭解完TCP連接的建立以及傳輸過程中的一些知識點後,再來了解TCP連接的終止就比較容易了。TCP連接中的雙方都可以主要斷開連接,斷開連接後的主機中的資源將被釋放,接下來我們先來了解終止過程的大概,如下圖:

1. TCP四次揮手的過程

  • 現在客戶端與服務端都處在連接建立的狀態,假設此時客戶端想要關閉連接;

  • 【第一個報文】:客戶端發送一個 FIN 報文,用來關閉客戶端到服務端的數據傳送,也就是客戶端告訴被服務端:我已經不會再給你發數據了(當然,在 FIN 包之前發送出去的數據,如果沒有收到對應的 ACK 報文,客戶端依舊會重發這些數據),但此時客戶端還可以接受數據;

  • 【第二個報文】:服務端收到 FIN 報文後,發送一個 ACK 給對方,確認序號爲收到序號 + 1,此時服務端進入 CLOSED_WAIT 狀態。客戶端接收到 ACK 報文後,進入 FIN_WAIT_2 狀態;

  • 【第三個報文】:服務端發送一個 FIN 報文,用來關閉被動關閉方到主動關閉方的數據傳送,也就是告訴主動關閉方,我的數據也發送完了,不會再給你發數據了,接下來服務端進入 LAST_ACK 狀態;

  • 【第四個報文】:客戶端收到 FIN 報文之後,發送一個 ACK 給服務端,確認應答號爲收到序號 + 1,此時客戶端進入 TIME_WAIT 狀態;

  • 服務端收到 FIN 報文後,就直接進入了 CLOSED 狀態,連接資源將被釋放,四次揮手到此結束;

  • 客戶端在經過 2MSL 時間後,自動進入CLOSED 狀態,至此客戶端也完成了連接的關閉。

2. 爲什麼需要四次揮手

再來回顧下四次揮手雙方發 FIN 包的過程,就能理解爲什麼需要四次了:

  • 關閉連接時,客戶端向服務端發送 FIN 時,僅僅表示客戶端不再發送數據了但是還能接收數據。
  • 服務器收到客戶端的 FIN 報文時,先回一個 ACK 應答報文,而服務端可能還有數據需要處理和發送,等服務端不再發送數據時,才發送 FIN 報文給客戶端來表示同意現在關閉連接。

從上面過程可知,服務端通常需要等待完成數據的發送和處理,所以服務端的 ACK 和 FIN 一般都會分開發送,從而比三次握手導致多了一次。

3. 爲什麼需要 TIME_WAIT

3.1 防止舊連接的數據包

這個過程比較容易理解,我們看下圖先:

如果沒有TIME_WAIT這個等待時間的話,像上圖中由於網絡延遲的消息,就有可能在下次連接建立的時候重新發送到接收端。因此TCP設計出在關閉連接時還需要等待 2 MSL這個時間,來確定是否還有後續的數據報發送過來,之後再關閉連接。

3.2 保證連接的正確關閉

TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.

也就是說,TIME-WAIT 作用是等待足夠的時間以確保最後的 ACK 能讓被動關閉方接收,從而幫助其正常關閉。

假設 TIME-WAIT 沒有等待時間或時間過短,斷開連接會造成什麼問題呢?

從上面我們可以瞭解到,如果沒有TIME_WAIT這段等待時間,那麼客戶端發送的 ACK應答包可能在網絡中丟失,此時由於 TIME_WAIT 過短或者沒有,那麼客戶端會直接進入 CLOSE 狀態,而服務端卻一直在 LAST_ACK 狀態下等待。那麼下一次連接的建立可能就無法成功。

如果TIME_WAIT的時間足夠長,那麼此時上面這種情況就得以解決:

  • 服務端正常收到四次揮手的最後一個 ACK 報文,則服務端正常關閉連接。
  • 服務端沒有收到四次揮手的最後一個 ACK 報文時,則會重發 FIN 關閉連接報文並等待新的 ACK 報文。

4. TIME_WAIT 爲什麼是 2 MSL

MSL 是 Maximum Segment Lifetime 的縮寫,譯爲報文最大生存時間,它是任何報文在網絡上存在的最長時間,超過這個時間報文將被丟棄。因爲 TCP 報文基於是 IP 協議的,而 IP 頭中有一個 TTL 字段,是 IP 數據報可以經過的最大路由數,每經過一個處理他的路由器此值就減 1,當此值爲 0 則數據報將被丟棄,同時發送 ICMP 報文通知源主機。

MSL 與 TTL 的區別: MSL 的單位是時間,而 TTL 是經過路由跳數。所以 MSL 應該要大於等於 TTL 消耗爲 0 的時間,以確保報文已被自然消亡。

TIME_WAIT 等待 2 倍的 MSL,比較合理的解釋是: 網絡中可能存在來自發送方的數據包,當這些發送方的數據包被接收方處理後又會向對方發送響應,所以一來一回需要等待 2 倍的時間

比如如果被動關閉方沒有收到斷開連接的最後的 ACK 報文,就會觸發超時重發 Fin 報文,另一方接收到 FIN 後,會重發 ACK 給被動關閉方, 一來一去正好 2 個 MSL。

2 MSL 的時間是從客戶端接收到 FIN 後發送 ACK 開始計時的。如果在 TIME_WAIT 時間內,因爲客戶端的 ACK 沒有傳輸到服務端,客戶端又接收到了服務端重發的 FIN 報文,那麼 2 MSL 時間將重新計時

在 Linux 系統裏 2 MSL 默認是 60 秒,那麼一個 MSL 也就是 30 秒。Linux 系統停留在 TIME_WAIT 的時間爲固定的 60 秒

本文絕大多數資料來源網上,只是爲了自己加深理解才寫篇博客整理總結,若是哪裏有不足之處請評論區指出。

這篇文章只是簡單的TCP協議入門,後續會繼續寫相關的文章。

參考資料:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章