什麼是TCP?

什麼是“TCP”

TCP 是一種面向連接的單播協議,在發送數據前,通信雙方必須在彼此間建立一條連接。

所謂的“連接”,其實是客戶端和服務器的內存裏保存的一份關於對方的信息,如 IP 地址、端口號等。

TCP 可以看成是一種字節流,它會處理 IP 層或以下的層的丟包、重複以及錯誤問題。

在連接的建立過程中,雙方需要交換一些連接的參數。這些參數可以放在 TCP 頭部。

TCP 提供了一種可靠、面向連接、字節流、傳輸層的服務,採用三次握手建立一個連接。採用四次揮手來關閉一個連接。

TCP報文頭部

在這裏插入圖片描述
TCP的三次握手是怎麼進行的:發送端發送一個SYN=1,ACK=0標誌的數據包給接收端,請求進行連接,這是第一次握手;接收端收到請求並且允許連接的話,就會發送一個SYN=1,ACK=1標誌的數據包給發送端,告訴它,可以通訊了,並且讓發送端發送一個確認數據包,這是第二次握手;最後,發送端發送一個SYN=0,ACK=1的數據包給接收端,告訴它連接已被確認,這就是第三次握手。之後,一個TCP連接建立,開始通訊。
所有的標誌可以理解爲key和value,key取值1或者0,當賦值1時代表這個指令啓用,key取值1時這個指令無效。

TCP協議通過使用“端口”來標識源端和目標端的應用進程。端口號可以使用0到65535之間的任何數字(其中0-1023端口屬於公認端口,綁定一些服務)。在收到服務請求時,操作系統動態地爲客戶端的應用程序分配端口號。

端口號:用來識別不同應用進程
源端口:標識報文的返回地址,即報文來源的地方(16bit)
目的端口:明確接收的計算上的應用接口(16bit)

順序號:用來標識從TCP源端向TCP目標端發送的數據字節流,它表示在這個報文段中的第一個數據字節。(32bit)

確認號:ACK標誌爲1時,確認號字段有效。它包含目標端所期望收到源端的下一個數據字節。(32bit)

頭部長度:給出頭部佔32比特的數目。如果沒有任何選項字段,TCP頭部長度爲20字節;最多可以有60字節的TCP頭部。(4bit)
標誌位字段(U、A、P、R、S、F):各比特的含義如下(6bit):
URG:緊急指針(urgent pointer)有效。(1bit)
ACK:確認序號(acknowledgement )有效。(1bit)
PSH:傳送(push)接收方應該儘快將這個報文段交給應用層。(1bit)
RST:(reset) 重建連接。(1bit)
SYN:(synchronous)發起一個連接。(1bit)
FIN:(finish結束)釋放一個連接。(1bit)
窗口:此字段用來進行流量控制。單位爲字節數,這個值是本機期望一次接收的字節數。(16bit)
校驗和:佔16比特。對整個TCP報文段,即TCP頭部和TCP數據進行校驗和計算,並由目標端進行驗證。(16bit)
緊急指針字段:佔16比特。它是一個偏移量,和序號字段中的值相加表示緊急數據最後一個字節的序號。(16bit)
選項和填充:可能包括"窗口擴大因子"、"時間"等選項。(32bit)

SYN:同步標誌
同步序列編號(Synchronize Sequence Numbers)欄有效。該標誌僅在三次握手建立TCP連接時有效。它提示TCP連接的服務端檢查序列編號,該序列編號爲TCP連接初始端(一般是客戶端)的初始序列編號。在這裏,可以把 TCP序列編號看作是一個範圍從0到4,294,967,295的32位計數器。通過TCP連接交換的數據中每一個字節都經過序列編號。在TCP報頭中的序列編號欄包括了TCP分段中第一個字節的序列編號。

ACK:確認標誌
確認編號(Acknowledgement Number)欄有效。大多數情況下該標誌位是置位的。TCP報頭內的確認編號欄內包含的確認編號(w+1,Figure-1)爲下一個預期的序列編號,同時提示遠端系統已經成功接收所有數據。

RST:復位標誌
復位標誌有效。用於復位相應的TCP連接。

URG:緊急標誌
緊急(The urgent pointer) 標誌有效。緊急標誌置位

PSH:推標誌
該標誌置位時,接收端不將該數據進行隊列處理,而是儘可能快將數據轉由應用處理。在處理 telnet 或 rlogin 等交互模式的連接時,該標誌總是置位的。

FIN:結束標誌
帶有該標誌置位的數據包用來結束一個TCP回話,但對應端口仍處於開放狀態,準備接收後續數據。

TCP的幾個狀態對於我們分析所起的作用
在TCP層,有個FLAGS字段,這個字段有以下幾個標識:SYN, FIN, ACK, PSH, RST, URG.其中,對於我們日常的分析有用的就是前面的五個字段。它們的含義是:SYN表示建立連接,FIN表示關閉連接,ACK表示響應,PSH表示有 DATA數據傳輸,RST表示連接重置。其中,ACK是可能與SYN,FIN等同時使用的,比如SYN和ACK可能同時爲1,它表示的就是建立連接之後的響應,如果只是單個的一個SYN,它表示的只是建立連接。

TCP的幾次握手就是通過這樣的ACK表現出來的。但SYN與FIN是不會同時爲1的,因爲前者表示的是建立連接,而後者表示的是斷開連接。RST一般是在FIN之後纔會出現爲1的情況,表示的是連接重置。一般地,當出現FIN包或RST包時,我們便認爲客戶端與服務器端斷開了連接;而當出現SYN和SYN+ACK包時,我們認爲客戶端與服務器建立了一個連接。PSH爲1的情況,一般只出現在 DATA內容不爲0的包中,也就是說PSH爲1表示的是有真正的TCP數據包內容被傳遞。TCP的連接建立和連接關閉,都是通過請求-響應的模式完成的。

TCP 服務模型

在瞭解了建立連接、關閉連接的“三次握手和四次揮手”後,我們再來看下 TCP 相關的東西。

一個 TCP 連接由一個 4 元組構成,分別是兩個 IP 地址和兩個端口號。一個 TCP 連接通常分爲三個階段:啓動、數據傳輸、退出(關閉)。

當 TCP 接收到另一端的數據時,它會發送一個確認,但這個確認不會立即發送,一般會延遲一會兒。

ACK 是累積的,一個確認字節號 N 的 ACK 表示所有直到 N 的字節(不包括 N)已經成功被接收了。

這樣的好處是如果一個 ACK 丟失,很可能後續的 ACK 就足以確認前面的報文段了。

一個完整的 TCP 連接是雙向和對稱的,數據可以在兩個方向上平等地流動,給上層應用程序提供一種雙工服務。

一旦建立了一個連接,這個連接的一個方向上的每個 TCP 報文段都包含了相反方向上的報文段的一個 ACK。

序列號的作用是使得一個 TCP 接收端可丟棄重複的報文段,記錄以雜亂次序到達的報文段。

因爲 TCP 使用 IP 來傳輸報文段,而 IP 不提供重複消除或者保證次序正確的功能。

另一方面,TCP 是一個字節流協議,絕不會以雜亂的次序給上層程序發送數據。

因此 TCP 接收端會被迫先保持大序列號的數據不交給應用程序,直到缺失的小序列號的報文段被填滿。

TCP 頭部

在這裏插入圖片描述
源端口和目的端口在 TCP 層確定雙方進程,序列號表示的是報文段數據中的第一個字節號,ACK 表示確認號。

該確認號的發送方期待接收的下一個序列號,即最後被成功接收的數據字節序列號加 1,這個字段只有在 ACK 位被啓用的時候纔有效。

當新建一個連接時,從客戶端發送到服務端的第一個報文段的 SYN 位被啓用,這稱爲 SYN 報文段。

這時序列號字段包含了在本次連接的這個方向上要使用的第一個序列號,即初始序列號 ISN,之後發送的數據是 ISN 加 1。

因此 SYN 位字段會消耗一個序列號。這意味着使用重傳進行可靠傳輸,而不消耗序列號的 ACK 則不是。

頭部長度(圖中的數據偏移)以 32 位字爲單位,也就是以 4bytes 爲單位,它只有 4 位,最大爲 15,因此頭部最大長度爲 60 字節,而其最小爲 5,也就是頭部最小爲 20 字節(可變選項爲空)。
在這裏插入圖片描述
當一個連接被建立或被終止時,交換的報文段只包含 TCP 頭部,而沒有數據。

狀態轉換

三次握手和四次揮手的狀態轉換如下圖所示
在這裏插入圖片描述

爲什麼要“三次握手,四次揮手”

三次握手

換個易於理解的視角來看爲什麼要三次握手。

客戶端和服務端通信前要進行連接,“三次握手”的作用就是雙方都能明確自己和對方的收、發能力是正常的。

第一次握手:客戶端發送網絡包,服務端收到了。這樣服務端就能得出結論:客戶端的發送能力、服務端的接收能力是正常的。

第二次握手:服務端發包,客戶端收到了。這樣客戶端就能得出結論:服務端的接收、發送能力,客戶端的接收、發送能力是正常的。

從客戶端的視角來看,我接到了服務端發送過來的響應數據包,說明服務端接收到了我在第一次握手時發送的網絡包,並且成功發送了響應數據包,這就說明,服務端的接收、發送能力正常。

而另一方面,我收到了服務端的響應數據包,說明我第一次發送的網絡包成功到達服務端,這樣,我自己的發送和接收能力也是正常的。

第三次握手:客戶端發包,服務端收到了。這樣服務端就能得出結論:客戶端的接收、發送能力,服務端的發送、接收能力是正常的。

第一、二次握手後,服務端並不知道客戶端的接收能力以及自己的發送能力是否正常。

而在第三次握手時,服務端收到了客戶端對第二次握手作的迴應。從服務端的角度,我在第二次握手時的響應數據發送出去了,客戶端接收到了。所以,我的發送能力是正常的。而客戶端的接收能力也是正常的。

經歷了上面的三次握手過程,客戶端和服務端都確認了自己的接收、發送能力是正常的。之後就可以正常通信了。

每次都是接收到數據包的一方可以得到一些結論,發送的一方其實沒有任何頭緒。

我雖然有發包的動作,但是我怎麼知道我有沒有發出去,而對方有沒有接收到呢?

而從上面的過程可以看到,最少是需要三次握手過程的。兩次達不到讓雙方都得出自己、對方的接收、發送能力都正常的結論。

其實每次收到網絡包的一方至少是可以得到:對方的發送、我方的接收是正常的。

而每一步都是有關聯的,下一次的“響應”是由於第一次的“請求”觸發,因此每次握手其實是可以得到額外的結論的。

比如第三次握手時,服務端收到數據包,表明看服務端只能得到客戶端的發送能力、服務端的接收能力是正常的。

但是結合第二次,說明服務端在第二次發送的響應包,客戶端接收到了,並且作出了響應,從而得到額外的結論:客戶端的接收、服務端的發送是正常的。

四次揮手

TCP 連接是雙向傳輸的對等的模式,就是說雙方都可以同時向對方發送或接收數據。

當有一方要關閉連接時,會發送指令告知對方,我要關閉連接了。這時對方會回一個 ACK,此時一個方向的連接關閉。

但是另一個方向仍然可以繼續傳輸數據,等到發送完了所有的數據後,會發送一個 FIN 段來關閉此方向上的連接。接收方發送 ACK 確認關閉連接。

注意,接收到 FIN 報文的一方只能回覆一個 ACK, 它是無法馬上返回對方一個 FIN 報文段的,因爲結束數據傳輸的“指令”是上層應用層給出的,我只是一個“搬運工”,我無法瞭解“上層的意志”。

“三次握手,四次揮手”怎麼完成?

其實三次握手的目的並不只是讓通信雙方都瞭解到一個連接正在建立,還在於利用數據包的選項來傳輸特殊的信息,交換初始序列號 ISN。

三次握手是指發送了 3 個報文段,四次揮手是指發送了 4 個報文段。注意,SYN 和 FIN 段都是會利用重傳進行可靠傳輸的。
在這裏插入圖片描述

三次握手原理:
  1. 客戶端發送一個 SYN 段,並指明客戶端的初始序列號,即 ISN©。
  2. 服務端發送自己的 SYN 段作爲應答,同樣指明自己的 ISN(s)。爲了確認客戶端的 SYN,將 ISN©+1 作爲 ACK 數值。這樣,每發送一個 SYN,序列號就會加 1,如果有丟失的情況,則會重傳。
  3. 爲了確認服務器端的 SYN,客戶端將 ISN(s)+1 作爲返回的 ACK 數值。
    在這裏插入圖片描述
四次揮手原理:
  1. 客戶端發送一個 FIN 段,幷包含一個希望接收者看到的自己當前的序列號 K。同時還包含一個 ACK 表示確認對方最近一次發過來的數據。
  2. 服務端將 K 值加 1 作爲 ACK 序號值,表明收到了上一個包。這時上層的應用程序會被告知另一端發起了關閉操作,通常這將引起應用程序發起自己的關閉操作。
  3. 服務端發起自己的 FIN 段,ACK=K+1, Seq=L。
  4. 客戶端確認。ACK=L+1
爲什麼建立連接是三次握手,而關閉連接卻是四次揮手呢?

這是因爲服務端在 LISTEN 狀態下,收到建立連接請求的 SYN 報文後,把 ACK 和 SYN 放在一個報文裏發送給客戶端。

而關閉連接時,當收到對方的 FIN 報文時,僅僅表示對方不再發送數據了但是還能接收數據,己方是否現在關閉發送數據通道,需要上層應用來決定,因此,己方 ACK 和 FIN 一般都會分開發送。

“三次握手,四次揮手”進階

ISN

三次握手的一個重要功能是客戶端和服務端交換 ISN(Initial Sequence Number), 以便讓對方知道接下來接收數據的時候如何按序列號組裝數據。

如果 ISN 是固定的,攻擊者很容易猜出後續的確認號:
ISN = M + F(localhost, localport, remotehost, remoteport)

M 是一個計時器,每隔 4 毫秒加 1。F 是一個 Hash 算法,根據源 IP、目的 IP、源端口、目的端口生成一個隨機數值。要保證 Hash 算法不能被外部輕易推算得出。

序列號迴繞

因爲 ISN 是隨機的,所以序列號容易就會超過 2^31-1。而 TCP 對於丟包和亂序等問題的判斷都是依賴於序列號大小比較的。

此時就出現了所謂的 TCP 序列號迴繞(sequence wraparound)問題怎麼解決?

/*
* The next routines deal with comparing 32 bit unsigned ints
* and worry about wraparound (automatic with unsigned arithmetic).
*/
static inline int before(__u32 seq1, __u32 seq2)
{
    return (__s32)(seq1-seq2) < 0;
}

#define after(seq2, seq1) before(seq1, seq2)

上述代碼是內核中的解決迴繞問題代碼。__s32 是有符號整型的意思,而 __u32 則是無符號整型。

序列號發生迴繞後,序列號變小,相減之後,把結果變成有符號數了,因此結果成了負數。

假設seq1=255, seq2=1(發生了迴繞)。
seq1 = 1111 1111 seq2 = 0000 0001
我們希望比較結果是
seq1 - seq2=
1111 1111
-0000 0001
-----------
1111 1110

由於我們將結果轉化成了有符號數,由於最高位是1,因此結果是一個負數,負數的絕對值爲
0000 0001 + 1 = 0000 0010 = 2

因此seq1 - seq2 < 0

SYN Flood 攻擊

最基本的 DoS 攻擊就是利用合理的服務請求來佔用過多的服務資源,從而使合法用戶無法得到服務的響應。SYN Flood 屬於 Dos 攻擊的一種。

如果惡意的向某個服務器端口發送大量的 SYN 包,則可以使服務器打開大量的半開連接,分配 TCB(Transmission Control Block), 從而消耗大量的服務器資源,同時也使得正常的連接請求無法被響應。

當開放了一個 TCP 端口後,該端口就處於 Listening 狀態,不停地監視發到該端口的 SYN 報文,一 旦接收到 Client 發來的 SYN 報文,就需要爲該請求分配一個 TCB。

通常一個 TCB 至少需要 280 個字節,在某些操作系統中 TCB 甚至需要 1300 個字節,並返回一個 SYN ACK 命令,立即轉爲 SYN-RECEIVED 即半開連接狀態,系統會爲此耗盡資源。常見的防攻擊方法有:

①無效連接的監視釋放

監視系統的半開連接和不活動連接,當達到一定閾值時拆除這些連接,從而釋放系統資源。

這種方法對於所有的連接一視同仁,而且由於 SYN Flood 造成的半開連接數量很大,正常連接請求也被淹沒在其中被這種方式誤釋放掉,因此這種方法屬於入門級的 SYN Flood 方法。

②延緩 TCB 分配方法

消耗服務器資源主要是因爲當 SYN 數據報文一到達,系統立即分配 TCB,從而佔用了資源。

而 SYN Flood 由於很難建立起正常連接,因此,當正常連接建立起來後再分配 TCB 則可以有效地減輕服務器資源的消耗。常見的方法是使用 Syn Cache 和 Syn Cookie 技術。

③Syn Cache 技術

系統在收到一個 SYN 報文時,在一個專用 Hash 表中保存這種半連接信息,直到收到正確的迴應 ACK 報文再分配 TCB。這個開銷遠小於 TCB 的開銷。當然還需要保存序列號。

④Syn Cookie 技術

Syn Cookie 技術則完全不使用任何存儲資源,這種方法比較巧妙,它使用一種特殊的算法生成 Sequence Number。

這種算法考慮到了對方的 IP、端口、己方 IP、端口的固定信息,以及對方無法知道而己方比較固定的一些信息。

如 MSS(Maximum Segment Size,最大報文段大小,指的是 TCP 報文的最大數據報長度,其中不包括 TCP 首部長度。)、時間等。

在收到對方的 ACK 報文後,重新計算一遍,看其是否與對方迴應報文中的(Sequence Number-1)相同,從而決定是否分配 TCB 資源。

⑤使用 SYN Proxy 防火牆

一種方式是防止牆 dqywb 連接的有效性後,防火牆纔會向內部服務器發起 SYN 請求。

防火牆代服務器發出的 SYN ACK 包使用的序列號爲 c, 而真正的服務器迴應的序列號爲 c’,。

這樣,在每個數據報文經過防火牆的時候進行序列號的修改。另一種方式是防火牆確定了連接的安全後,會發出一個 safe reset 命令,Client 會進行重新連接,這時出現的 SYN 報文會直接放行。

這樣不需要修改序列號了。但是,Client 需要發起兩次握手過程,因此建立連接的時間將會延長。

連接隊列

在外部請求到達時,被服務程序最終感知到前,連接可能處於 SYN_RCVD 狀態或是 ESTABLISHED 狀態,但還未被應用程序接受。
在這裏插入圖片描述
對應地,服務器端也會維護兩種隊列,處於 SYN_RCVD 狀態的半連接隊列,而處於 ESTABLISHED 狀態但仍未被應用程序 Accept 的爲全連接隊列。

如果這兩個隊列滿了之後,就會出現各種丟包的情形:

查看是否有連接溢出
netstat -s | grep LISTEN

半連接隊列滿了

條目所標識的連接在服務器處於 Syn_RECV 狀態,當服務器收到客戶的確認包時,刪除該條目,服務器進入 ESTABLISHED 狀態。

目前,Linux 下默認會進行 5 次重發 SYN-ACK 包,重試的間隔時間從 1s 開始,下次的重試間隔時間是前一次的雙倍,5 次的重試時間間隔爲 1s, 2s, 4s, 8s, 16s, 總共 31s,稱爲指數退避。

第 5 次發出後還要等 32s 才知道第 5 次也超時了,所以,總共需要 1s + 2s + 4s + 8s + 16s + 32s = 63s,TCP 纔會斷開這個連接。

由於,SYN 超時需要 63 秒,那麼就給攻擊者一個攻擊服務器的機會,攻擊者在短時間內發送大量的 SYN 包給 Server(俗稱 SYN Flood 攻擊),用於耗盡 Server 的 SYN 隊列。

對於應對 SYN 過多的問題,Linux 提供了幾個 TCP 參數來調整應對:
在這裏插入圖片描述

全連接隊列滿了

當第三次握手時,當 Server 接收到 ACK 包之後,會進入一個新的叫 Accept 的隊列。

當 Accept 隊列滿了之後,即使 Client 繼續向 Server 發送 ACK 的包,也會不被響應。

此時 ListenOverflows+1,同時 Server 通過 tcp_abort_on_overflow 來決定如何返回,0 表示直接丟棄該 ACK,1 表示發送 RST 通知 Client。

相應的,Client 則會分別返回 read timeout 或者 connection reset by peer。

另外,tcp_abort_on_overflow 是 0 的話,Server 過一段時間再次發送 SYN+ACK 給 Client(也就是重新走握手的第二步),如果 Client 超時等待比較短,就很容易異常了。

而客戶端收到多個 SYN ACK 包,則會認爲之前的 ACK 丟包了。於是促使客戶端再次發送 ACK ,在 Accept 隊列有空閒的時候最終完成連接。

若 Accept 隊列始終滿員,則最終客戶端收到 RST 包(此時服務端發送 SYN+ACK 的次數超出了 tcp_synack_retries)。

服務端僅僅只是創建一個定時器,以固定間隔重傳 SYN 和 ACK 到服務端:
在這裏插入圖片描述

命令

netstat -s 命令:

[root@server ~]#  netstat -s | egrep "listen|LISTEN"
667399 times the listen queue of a socket overflowed
667399 SYNs to LISTEN sockets ignored

上面看到的 667399 times ,表示全連接隊列溢出的次數,隔幾秒鐘執行下,如果這個數字一直在增加的話肯定全連接隊列偶爾滿了。

查看 Accept queue 是否有溢出:

[root@server ~]#  netstat -s | grep TCPBacklogDrop

ss命令:

[root@server ~]#  ss -lnt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN     0      128 *:6379 *:*
LISTEN     0      128 *:22 *:*

如果 State 是 Listen 狀態,Send-Q 表示第三列的 Listen 端口上的全連接隊列最大爲 50,第一列 Recv-Q 爲全連接隊列當前使用了多少。

非 LISTEN 狀態中 Recv-Q 表示 receive queue 中的 bytes 數量;Send-Q 表示 send queue 中的 bytes 數值。

小結:當外部連接請求到來時,TCP 模塊會首先查看 max_syn_backlog,如果處於 SYN_RCVD 狀態的連接數目超過這一閾值,進入的連接會被拒絕。

根據 tcp_abort_on_overflow 字段來決定是直接丟棄,還是直接 reset。

從服務端來說,三次握手中,第一步 Server 接受到 Client 的 SYN 後,把相關信息放到半連接隊列中,同時回覆 SYN+ACK 給 Client。第三步當收到客戶端的 ACK,將連接加入到全連接隊列。

一般,全連接隊列比較小,會先滿,此時半連接隊列還沒滿。如果這時收到 SYN 報文,則會進入半連接隊列,沒有問題。

但是如果收到了三次握手中的第 3 步(ACK),則會根據 tcp_abort_on_overflow 字段來決定是直接丟棄,還是直接 reset。

此時,客戶端發送了 ACK, 那麼客戶端認爲三次握手完成,它認爲服務端已經準備好了接收數據的準備。

但此時服務端可能因爲全連接隊列滿了而無法將連接放入,會重新發送第 2 步的 SYN+ACK,如果這時有數據到來,服務器 TCP 模塊會將數據存入隊列中。

一段時間後,Client 端沒收到回覆,超時,連接異常,Client 會主動關閉連接。

“三次握手,四次揮手”Redis 實例分析

①我在 dev 機器上部署 Redis 服務,端口號爲 6379,通過 tcpdump 工具獲取數據包,使用如下命令:

tcpdump -w /tmp/a.cap port 6379 -s0
-w把數據寫入文件,-s0設置每個數據包的大小默認爲68字節,如果用-S0則會抓到完整數據包

②在 dev2 機器上用 redis-cli 訪問 dev:6379, 發送一個 ping, 得到回覆 pong,停止抓包,用 tcpdump 讀取捕獲到的數據包:

tcpdump -r /tmp/a.cap -n -nn -A -x| vim --x 以16進制形式展示,便於後面分析)

共收到了 7 個包。

抓到的是 IP 數據包,IP 數據包分爲 IP 頭部和 IP 數據部分,IP 數據部分是 TCP 頭部加 TCP 數據部分。

IP 的數據格式爲:

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
這樣第一個包分析完了。dev2 向 dev 發送 SYN 請求。也就是三次握手中的第一次了。

SYN seq(c)=4133153791

第二個包,dev 響應連接,ack=4133153792。表明 dev 下次準備接收這個序號的包,用於 tcp 字節注的順序控制。dev(也就是 server 端)的初始序號爲 seq=4264776963, syn=1。

SYN ack=seq(c)+1 seq(s)=4264776963

第三個包,client 包確認,這裏使用了相對值應答。seq=4133153792,等於第二個包的 ack. ack=4264776964。

ack=seq(s)+1, seq=seq(c)+1

至此,三次握手完成。接下來就是發送 ping 和 pong 的數據了。
在這裏插入圖片描述
tcp 首部長度爲 32B,可選長度爲 12B。IP 報文的總長度爲 66B,首部長度爲 20B,因此 TCP 數據部分長度爲 14B. seq=0xf65a ec00=4133153792。

ACK, PSH. 數據部分爲 2a31 0d0a 2434 0d0a 7069 6e67 0d0a:

0x2a31         -> *1
0x0d0a         -> \r\n
0x2434         -> $4
0x0d0a         -> \r\n
0x7069 0x6e67  -> ping
0x0d0a         -> \r\n

dev2 向 dev 發送了 ping 數據,第四個包完畢。第五個包,dev2 向 dev 發送 ack 響應。

序列號爲 0xfe33 5504=4264776964,ack 確認號爲 0xf65a ec0e=4133153806=(4133153792+14)。

第六個包,dev 向 dev2 響應 pong 消息。序列號 fe33 5504,確認號 f65a ec0e,TCP 頭部可選長度爲 12B,IP 數據報總長度爲 59B,首部長度爲 20B,因此 TCP 數據長度爲 7B。

數據部分 2b50 4f4e 470d 0a,翻譯過來就是+PONG\r\n。至此,Redis 客戶端和 Server 端的三次握手過程分析完畢。

總結

“三次握手,四次揮手”看似簡單,但是深究進去,還是可以延伸出很多知識點的,比如半連接隊列、全連接隊列等等。

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