自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

TCP三次握手,四次揮手,你真的懂嗎?

網(wǎng)絡(luò) 通信技術(shù) 開發(fā)工具
記得剛畢業(yè)找工作面試的時(shí)候,經(jīng)常會(huì)被問到:你知道“三次握手,四次揮手”嗎?

 記得剛畢業(yè)找工作面試的時(shí)候,經(jīng)常會(huì)被問到:你知道“三次握手,四次揮手”嗎?

本文來自于作者投稿,公眾號(hào):碼農(nóng)桃花源

這時(shí)候我會(huì)“胸有成竹”地“背誦”前期準(zhǔn)備好的“答案”,第一次怎么怎么,第二次……答完后就沒有下文了,面試官貌似也沒有深入下去的意思,深入下去我也不懂,皆大歡喜!

作為程序員,要有“刨根問底”的精神,知其然,更要知其所以然。這篇文章希望能抽絲剝繭,還原背后的原理。

什么是“三次握手,四次揮手”

TCP 是一種面向連接的單播協(xié)議,在發(fā)送數(shù)據(jù)前,通信雙方必須在彼此間建立一條連接。

所謂的“連接”,其實(shí)是客戶端和服務(wù)器的內(nèi)存里保存的一份關(guān)于對(duì)方的信息,如 IP 地址、端口號(hào)等。

TCP 可以看成是一種字節(jié)流,它會(huì)處理 IP 層或以下的層的丟包、重復(fù)以及錯(cuò)誤問題。

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

TCP 提供了一種可靠、面向連接、字節(jié)流、傳輸層的服務(wù),采用三次握手建立一個(gè)連接。采用四次揮手來關(guān)閉一個(gè)連接。

TCP 服務(wù)模型

在了解了建立連接、關(guān)閉連接的“三次握手和四次揮手”后,我們?cè)賮砜聪?TCP 相關(guān)的東西。

一個(gè) TCP 連接由一個(gè) 4 元組構(gòu)成,分別是兩個(gè) IP 地址和兩個(gè)端口號(hào)。一個(gè) TCP 連接通常分為三個(gè)階段:啟動(dòng)、數(shù)據(jù)傳輸、退出(關(guān)閉)。

當(dāng) TCP 接收到另一端的數(shù)據(jù)時(shí),它會(huì)發(fā)送一個(gè)確認(rèn),但這個(gè)確認(rèn)不會(huì)立即發(fā)送,一般會(huì)延遲一會(huì)兒。

ACK 是累積的,一個(gè)確認(rèn)字節(jié)號(hào) N 的 ACK 表示所有直到 N 的字節(jié)(不包括 N)已經(jīng)成功被接收了。

這樣的好處是如果一個(gè) ACK 丟失,很可能后續(xù)的 ACK 就足以確認(rèn)前面的報(bào)文段了。

一個(gè)完整的 TCP 連接是雙向和對(duì)稱的,數(shù)據(jù)可以在兩個(gè)方向上平等地流動(dòng),給上層應(yīng)用程序提供一種雙工服務(wù)。

一旦建立了一個(gè)連接,這個(gè)連接的一個(gè)方向上的每個(gè) TCP 報(bào)文段都包含了相反方向上的報(bào)文段的一個(gè) ACK。

序列號(hào)的作用是使得一個(gè) TCP 接收端可丟棄重復(fù)的報(bào)文段,記錄以雜亂次序到達(dá)的報(bào)文段。

因?yàn)?TCP 使用 IP 來傳輸報(bào)文段,而 IP 不提供重復(fù)消除或者保證次序正確的功能。

另一方面,TCP 是一個(gè)字節(jié)流協(xié)議,絕不會(huì)以雜亂的次序給上層程序發(fā)送數(shù)據(jù)。

因此 TCP 接收端會(huì)被迫先保持大序列號(hào)的數(shù)據(jù)不交給應(yīng)用程序,直到缺失的小序列號(hào)的報(bào)文段被填滿。

TCP 頭部

源端口和目的端口在 TCP 層確定雙方進(jìn)程,序列號(hào)表示的是報(bào)文段數(shù)據(jù)中的第一個(gè)字節(jié)號(hào),ACK 表示確認(rèn)號(hào)。

該確認(rèn)號(hào)的發(fā)送方期待接收的下一個(gè)序列號(hào),即最后被成功接收的數(shù)據(jù)字節(jié)序列號(hào)加 1,這個(gè)字段只有在 ACK 位被啟用的時(shí)候才有效。

當(dāng)新建一個(gè)連接時(shí),從客戶端發(fā)送到服務(wù)端的第一個(gè)報(bào)文段的 SYN 位被啟用,這稱為 SYN 報(bào)文段。

這時(shí)序列號(hào)字段包含了在本次連接的這個(gè)方向上要使用的第一個(gè)序列號(hào),即初始序列號(hào) ISN,之后發(fā)送的數(shù)據(jù)是 ISN 加 1。

因此 SYN 位字段會(huì)消耗一個(gè)序列號(hào)。這意味著使用重傳進(jìn)行可靠傳輸,而不消耗序列號(hào)的 ACK 則不是。

頭部長度(圖中的數(shù)據(jù)偏移)以 32 位字為單位,也就是以 4bytes 為單位,它只有 4 位,最大為 15,因此頭部最大長度為 60 字節(jié),而其最小為 5,也就是頭部最小為 20 字節(jié)(可變選項(xiàng)為空)。

  • ACK —— 確認(rèn),使得確認(rèn)號(hào)有效。
  • RST —— 重置連接(經(jīng)??吹降?reset by peer)就是此字段搞的鬼。
  • SYN —— 用于初如化一個(gè)連接的序列號(hào)。
  • FIN —— 該報(bào)文段的發(fā)送方已經(jīng)結(jié)束向?qū)Ψ桨l(fā)送數(shù)據(jù)。

當(dāng)一個(gè)連接被建立或被終止時(shí),交換的報(bào)文段只包含 TCP 頭部,而沒有數(shù)據(jù)。

狀態(tài)轉(zhuǎn)換

三次握手和四次揮手的狀態(tài)轉(zhuǎn)換如下圖:

為什么要“三次握手,四次揮手”

三次握手

換個(gè)易于理解的視角來看為什么要三次握手。

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

第一次握手:客戶端發(fā)送網(wǎng)絡(luò)包,服務(wù)端收到了。這樣服務(wù)端就能得出結(jié)論:客戶端的發(fā)送能力、服務(wù)端的接收能力是正常的。

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

從客戶端的視角來看,我接到了服務(wù)端發(fā)送過來的響應(yīng)數(shù)據(jù)包,說明服務(wù)端接收到了我在第一次握手時(shí)發(fā)送的網(wǎng)絡(luò)包,并且成功發(fā)送了響應(yīng)數(shù)據(jù)包,這就說明,服務(wù)端的接收、發(fā)送能力正常。

而另一方面,我收到了服務(wù)端的響應(yīng)數(shù)據(jù)包,說明我第一次發(fā)送的網(wǎng)絡(luò)包成功到達(dá)服務(wù)端,這樣,我自己的發(fā)送和接收能力也是正常的。

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

第一、二次握手后,服務(wù)端并不知道客戶端的接收能力以及自己的發(fā)送能力是否正常。

而在第三次握手時(shí),服務(wù)端收到了客戶端對(duì)第二次握手作的回應(yīng)。從服務(wù)端的角度,我在第二次握手時(shí)的響應(yīng)數(shù)據(jù)發(fā)送出去了,客戶端接收到了。所以,我的發(fā)送能力是正常的。而客戶端的接收能力也是正常的。

經(jīng)歷了上面的三次握手過程,客戶端和服務(wù)端都確認(rèn)了自己的接收、發(fā)送能力是正常的。之后就可以正常通信了。

每次都是接收到數(shù)據(jù)包的一方可以得到一些結(jié)論,發(fā)送的一方其實(shí)沒有任何頭緒。

我雖然有發(fā)包的動(dòng)作,但是我怎么知道我有沒有發(fā)出去,而對(duì)方有沒有接收到呢?

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

其實(shí)每次收到網(wǎng)絡(luò)包的一方至少是可以得到:對(duì)方的發(fā)送、我方的接收是正常的。

而每一步都是有關(guān)聯(lián)的,下一次的“響應(yīng)”是由于第一次的“請(qǐng)求”觸發(fā),因此每次握手其實(shí)是可以得到額外的結(jié)論的。

比如第三次握手時(shí),服務(wù)端收到數(shù)據(jù)包,表明看服務(wù)端只能得到客戶端的發(fā)送能力、服務(wù)端的接收能力是正常的。

但是結(jié)合第二次,說明服務(wù)端在第二次發(fā)送的響應(yīng)包,客戶端接收到了,并且作出了響應(yīng),從而得到額外的結(jié)論:客戶端的接收、服務(wù)端的發(fā)送是正常的。

用表格總結(jié)一下:

四次揮手

TCP 連接是雙向傳輸?shù)膶?duì)等的模式,就是說雙方都可以同時(shí)向?qū)Ψ桨l(fā)送或接收數(shù)據(jù)。

當(dāng)有一方要關(guān)閉連接時(shí),會(huì)發(fā)送指令告知對(duì)方,我要關(guān)閉連接了。這時(shí)對(duì)方會(huì)回一個(gè) ACK,此時(shí)一個(gè)方向的連接關(guān)閉。

但是另一個(gè)方向仍然可以繼續(xù)傳輸數(shù)據(jù),等到發(fā)送完了所有的數(shù)據(jù)后,會(huì)發(fā)送一個(gè) FIN 段來關(guān)閉此方向上的連接。接收方發(fā)送 ACK 確認(rèn)關(guān)閉連接。

注意,接收到 FIN 報(bào)文的一方只能回復(fù)一個(gè) ACK, 它是無法馬上返回對(duì)方一個(gè) FIN 報(bào)文段的,因?yàn)榻Y(jié)束數(shù)據(jù)傳輸?shù)?ldquo;指令”是上層應(yīng)用層給出的,我只是一個(gè)“搬運(yùn)工”,我無法了解“上層的意志”。

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

其實(shí)三次握手的目的并不只是讓通信雙方都了解到一個(gè)連接正在建立,還在于利用數(shù)據(jù)包的選項(xiàng)來傳輸特殊的信息,交換初始序列號(hào) ISN。

三次握手是指發(fā)送了 3 個(gè)報(bào)文段,四次揮手是指發(fā)送了 4 個(gè)報(bào)文段。注意,SYN 和 FIN 段都是會(huì)利用重傳進(jìn)行可靠傳輸?shù)摹?/p>

三次握手原理:

  • 客戶端發(fā)送一個(gè) SYN 段,并指明客戶端的初始序列號(hào),即 ISN(c)。
  • 服務(wù)端發(fā)送自己的 SYN 段作為應(yīng)答,同樣指明自己的 ISN(s)。為了確認(rèn)客戶端的 SYN,將 ISN(c)+1 作為 ACK 數(shù)值。這樣,每發(fā)送一個(gè) SYN,序列號(hào)就會(huì)加 1,如果有丟失的情況,則會(huì)重傳。
  • 為了確認(rèn)服務(wù)器端的 SYN,客戶端將 ISN(s)+1 作為返回的 ACK 數(shù)值。

四次揮手原理:

  • 客戶端發(fā)送一個(gè) FIN 段,并包含一個(gè)希望接收者看到的自己當(dāng)前的序列號(hào) K。同時(shí)還包含一個(gè) ACK 表示確認(rèn)對(duì)方最近一次發(fā)過來的數(shù)據(jù)。
  • 服務(wù)端將 K 值加 1 作為 ACK 序號(hào)值,表明收到了上一個(gè)包。這時(shí)上層的應(yīng)用程序會(huì)被告知另一端發(fā)起了關(guān)閉操作,通常這將引起應(yīng)用程序發(fā)起自己的關(guān)閉操作。
  • 服務(wù)端發(fā)起自己的 FIN 段,ACK=K+1, Seq=L。
  • 客戶端確認(rèn)。ACK=L+1

為什么建立連接是三次握手,而關(guān)閉連接卻是四次揮手呢?這是因?yàn)榉?wù)端在 LISTEN 狀態(tài)下,收到建立連接請(qǐng)求的 SYN 報(bào)文后,把 ACK 和 SYN 放在一個(gè)報(bào)文里發(fā)送給客戶端。

而關(guān)閉連接時(shí),當(dāng)收到對(duì)方的 FIN 報(bào)文時(shí),僅僅表示對(duì)方不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù),己方是否現(xiàn)在關(guān)閉發(fā)送數(shù)據(jù)通道,需要上層應(yīng)用來決定,因此,己方 ACK 和 FIN 一般都會(huì)分開發(fā)送。

“三次握手,四次揮手”進(jìn)階

ISN

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

如果 ISN 是固定的,攻擊者很容易猜出后續(xù)的確認(rèn)號(hào):

  1. ISN = M + F(localhost, localport, remotehost, remoteport) 

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

序列號(hào)回繞

因?yàn)?ISN 是隨機(jī)的,所以序列號(hào)容易就會(huì)超過 2^31-1。而 TCP 對(duì)于丟包和亂序等問題的判斷都是依賴于序列號(hào)大小比較的。

此時(shí)就出現(xiàn)了所謂的 TCP 序列號(hào)回繞(sequence wraparound)問題怎么解決?

  1. /* 
  2. * The next routines deal with comparing 32 bit unsigned ints 
  3. and worry about wraparound (automatic with unsigned arithmetic). 
  4. */ 
  5. static inline int before(__u32 seq1, __u32 seq2) 
  6.     return (__s32)(seq1-seq2) < 0; 
  7.  
  8. #define after(seq2, seq1) before(seq1, seq2) 

上述代碼是內(nèi)核中的解決回繞問題代碼。__s32 是有符號(hào)整型的意思,而 __u32 則是無符號(hào)整型。

序列號(hào)發(fā)生回繞后,序列號(hào)變小,相減之后,把結(jié)果變成有符號(hào)數(shù)了,因此結(jié)果成了負(fù)數(shù)。

  1. 假設(shè)seq1=255, seq2=1(發(fā)生了回繞)。 
  2. seq1 = 1111 1111 seq2 = 0000 0001 
  3. 我們希望比較結(jié)果是 
  4. seq1 - seq2= 
  5. 1111 1111 
  6. -0000 0001 
  7. ----------- 
  8. 1111 1110 
  9.  
  10. 由于我們將結(jié)果轉(zhuǎn)化成了有符號(hào)數(shù),由于最高位是1,因此結(jié)果是一個(gè)負(fù)數(shù),負(fù)數(shù)的絕對(duì)值為 
  11. 0000 0001 + 1 = 0000 0010 = 2 
  12.  
  13. 因此seq1 - seq2 < 0 

SYN Flood 攻擊

最基本的 DoS 攻擊就是利用合理的服務(wù)請(qǐng)求來占用過多的服務(wù)資源,從而使合法用戶無法得到服務(wù)的響應(yīng)。SYN Flood 屬于 Dos 攻擊的一種。

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

當(dāng)開放了一個(gè) TCP 端口后,該端口就處于 Listening 狀態(tài),不停地監(jiān)視發(fā)到該端口的 SYN 報(bào)文,一 旦接收到 Client 發(fā)來的 SYN 報(bào)文,就需要為該請(qǐng)求分配一個(gè) TCB。

通常一個(gè) TCB 至少需要 280 個(gè)字節(jié),在某些操作系統(tǒng)中 TCB 甚至需要 1300 個(gè)字節(jié),并返回一個(gè) SYN ACK 命令,立即轉(zhuǎn)為 SYN-RECEIVED 即半開連接狀態(tài),系統(tǒng)會(huì)為此耗盡資源。常見的防攻擊方法有:

①無效連接的監(jiān)視釋放

監(jiān)視系統(tǒng)的半開連接和不活動(dòng)連接,當(dāng)達(dá)到一定閾值時(shí)拆除這些連接,從而釋放系統(tǒng)資源。

這種方法對(duì)于所有的連接一視同仁,而且由于 SYN Flood 造成的半開連接數(shù)量很大,正常連接請(qǐng)求也被淹沒在其中被這種方式誤釋放掉,因此這種方法屬于入門級(jí)的 SYN Flood 方法。

②延緩 TCB 分配方法

消耗服務(wù)器資源主要是因?yàn)楫?dāng) SYN 數(shù)據(jù)報(bào)文一到達(dá),系統(tǒng)立即分配 TCB,從而占用了資源。

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

③Syn Cache 技術(shù)

系統(tǒng)在收到一個(gè) SYN 報(bào)文時(shí),在一個(gè)專用 Hash 表中保存這種半連接信息,直到收到正確的回應(yīng) ACK 報(bào)文再分配 TCB。這個(gè)開銷遠(yuǎn)小于 TCB 的開銷。當(dāng)然還需要保存序列號(hào)。

④Syn Cookie 技術(shù)

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

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

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

在收到對(duì)方的 ACK 報(bào)文后,重新計(jì)算一遍,看其是否與對(duì)方回應(yīng)報(bào)文中的(Sequence Number-1)相同,從而決定是否分配 TCB 資源。

⑤使用 SYN Proxy 防火墻

一種方式是防止墻 dqywb 連接的有效性后,防火墻才會(huì)向內(nèi)部服務(wù)器發(fā)起 SYN 請(qǐng)求。

防火墻代服務(wù)器發(fā)出的 SYN ACK 包使用的序列號(hào)為 c, 而真正的服務(wù)器回應(yīng)的序列號(hào)為 c',。

這樣,在每個(gè)數(shù)據(jù)報(bào)文經(jīng)過防火墻的時(shí)候進(jìn)行序列號(hào)的修改。另一種方式是防火墻確定了連接的安全后,會(huì)發(fā)出一個(gè) safe reset 命令,Client 會(huì)進(jìn)行重新連接,這時(shí)出現(xiàn)的 SYN 報(bào)文會(huì)直接放行。

這樣不需要修改序列號(hào)了。但是,Client 需要發(fā)起兩次握手過程,因此建立連接的時(shí)間將會(huì)延長。

連接隊(duì)列

在外部請(qǐng)求到達(dá)時(shí),被服務(wù)程序最終感知到前,連接可能處于 SYN_RCVD 狀態(tài)或是 ESTABLISHED 狀態(tài),但還未被應(yīng)用程序接受。

對(duì)應(yīng)地,服務(wù)器端也會(huì)維護(hù)兩種隊(duì)列,處于 SYN_RCVD 狀態(tài)的半連接隊(duì)列,而處于 ESTABLISHED 狀態(tài)但仍未被應(yīng)用程序 Accept 的為全連接隊(duì)列。

如果這兩個(gè)隊(duì)列滿了之后,就會(huì)出現(xiàn)各種丟包的情形:

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

半連接隊(duì)列滿了

在三次握手協(xié)議中,服務(wù)器維護(hù)一個(gè)半連接隊(duì)列,該隊(duì)列為每個(gè)客戶端的 SYN 包開設(shè)一個(gè)條目(服務(wù)端在接收到 SYN 包的時(shí)候,就已經(jīng)創(chuàng)建了request_sock結(jié)構(gòu),存儲(chǔ)在半連接隊(duì)列中)。

該條目表明服務(wù)器已收到 SYN 包,并向客戶發(fā)出確認(rèn),正在等待客戶的確認(rèn)包。

這些條目所標(biāo)識(shí)的連接在服務(wù)器處于 Syn_RECV 狀態(tài),當(dāng)服務(wù)器收到客戶的確認(rèn)包時(shí),刪除該條目,服務(wù)器進(jìn)入 ESTABLISHED 狀態(tài)。

目前,Linux 下默認(rèn)會(huì)進(jìn)行 5 次重發(fā) SYN-ACK 包,重試的間隔時(shí)間從 1s 開始,下次的重試間隔時(shí)間是前一次的雙倍,5 次的重試時(shí)間間隔為 1s, 2s, 4s, 8s, 16s, 總共 31s,稱為指數(shù)退避。

第 5 次發(fā)出后還要等 32s 才知道第 5 次也超時(shí)了,所以,總共需要 1s + 2s + 4s + 8s + 16s + 32s = 63s,TCP 才會(huì)斷開這個(gè)連接。

由于,SYN 超時(shí)需要 63 秒,那么就給攻擊者一個(gè)攻擊服務(wù)器的機(jī)會(huì),攻擊者在短時(shí)間內(nèi)發(fā)送大量的 SYN 包給 Server(俗稱 SYN Flood 攻擊),用于耗盡 Server 的 SYN 隊(duì)列。

對(duì)于應(yīng)對(duì) SYN 過多的問題,Linux 提供了幾個(gè) TCP 參數(shù)來調(diào)整應(yīng)對(duì):

  • tcp_syncookies
  • tcp_synack_retries
  • tcp_max_syn_backlog
  • tcp_abort_on_overflow

全連接隊(duì)列滿了

當(dāng)?shù)谌挝帐謺r(shí),當(dāng) Server 接收到 ACK 包之后,會(huì)進(jìn)入一個(gè)新的叫 Accept 的隊(duì)列。

當(dāng) Accept 隊(duì)列滿了之后,即使 Client 繼續(xù)向 Server 發(fā)送 ACK 的包,也會(huì)不被響應(yīng)。

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

相應(yīng)的,Client 則會(huì)分別返回 read timeout 或者 connection reset by peer。

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

而客戶端收到多個(gè) SYN ACK 包,則會(huì)認(rèn)為之前的 ACK 丟包了。于是促使客戶端再次發(fā)送 ACK ,在 Accept 隊(duì)列有空閑的時(shí)候最終完成連接。

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

服務(wù)端僅僅只是創(chuàng)建一個(gè)定時(shí)器,以固定間隔重傳 SYN 和 ACK 到服務(wù)端:

命令

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

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

查看 Accept queue 是否有溢出:

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

ss 命令:

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

如果 State 是 Listen 狀態(tài),Send-Q 表示第三列的 Listen 端口上的全連接隊(duì)列最大為 50,第一列 Recv-Q 為全連接隊(duì)列當(dāng)前使用了多少。

非 LISTEN 狀態(tài)中 Recv-Q 表示 receive queue 中的 bytes 數(shù)量;Send-Q 表示 send queue 中的 bytes 數(shù)值。

小結(jié):當(dāng)外部連接請(qǐng)求到來時(shí),TCP 模塊會(huì)首先查看 max_syn_backlog,如果處于 SYN_RCVD 狀態(tài)的連接數(shù)目超過這一閾值,進(jìn)入的連接會(huì)被拒絕。

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

從服務(wù)端來說,三次握手中,第一步 Server 接受到 Client 的 SYN 后,把相關(guān)信息放到半連接隊(duì)列中,同時(shí)回復(fù) SYN+ACK 給 Client。第三步當(dāng)收到客戶端的 ACK,將連接加入到全連接隊(duì)列。

一般,全連接隊(duì)列比較小,會(huì)先滿,此時(shí)半連接隊(duì)列還沒滿。如果這時(shí)收到 SYN 報(bào)文,則會(huì)進(jìn)入半連接隊(duì)列,沒有問題。

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

此時(shí),客戶端發(fā)送了 ACK, 那么客戶端認(rèn)為三次握手完成,它認(rèn)為服務(wù)端已經(jīng)準(zhǔn)備好了接收數(shù)據(jù)的準(zhǔn)備。

但此時(shí)服務(wù)端可能因?yàn)槿B接隊(duì)列滿了而無法將連接放入,會(huì)重新發(fā)送第 2 步的 SYN+ACK,如果這時(shí)有數(shù)據(jù)到來,服務(wù)器 TCP 模塊會(huì)將數(shù)據(jù)存入隊(duì)列中。

一段時(shí)間后,Client 端沒收到回復(fù),超時(shí),連接異常,Client 會(huì)主動(dòng)關(guān)閉連接。

“三次握手,四次揮手”Redis 實(shí)例分析

①我在 dev 機(jī)器上部署 Redis 服務(wù),端口號(hào)為 6379,通過 tcpdump 工具獲取數(shù)據(jù)包,使用如下命令:

  1. tcpdump -w /tmp/a.cap port 6379 -s0 
  2. -w把數(shù)據(jù)寫入文件,-s0設(shè)置每個(gè)數(shù)據(jù)包的大小默認(rèn)為68字節(jié),如果用-S0則會(huì)抓到完整數(shù)據(jù)包 

②在 dev2 機(jī)器上用 redis-cli 訪問 dev:6379, 發(fā)送一個(gè) ping, 得到回復(fù) pong,停止抓包,用 tcpdump 讀取捕獲到的數(shù)據(jù)包:

  1. tcpdump -r /tmp/a.cap -n -nn -A -x| vim - 
  2. (-x 以16進(jìn)制形式展示,便于后面分析) 

共收到了 7 個(gè)包。

抓到的是 IP 數(shù)據(jù)包,IP 數(shù)據(jù)包分為 IP 頭部和 IP 數(shù)據(jù)部分,IP 數(shù)據(jù)部分是 TCP 頭部加 TCP 數(shù)據(jù)部分。

IP 的數(shù)據(jù)格式為:

它由固定長度 20B+ 可變長度構(gòu)成。

 

對(duì)著 IP 頭部格式,來拆解數(shù)據(jù)包的具體含義。

剩余的數(shù)據(jù)部分即為 TCP 協(xié)議相關(guān)的。TCP 也是 20B 固定長度+可變長度部分。

可變長度部分,協(xié)議如下:

這樣第一個(gè)包分析完了。dev2 向 dev 發(fā)送 SYN 請(qǐng)求。也就是三次握手中的第一次了。

  1. SYN seq(c)=4133153791 

第二個(gè)包,dev 響應(yīng)連接,ack=4133153792。表明 dev 下次準(zhǔn)備接收這個(gè)序號(hào)的包,用于 tcp 字節(jié)注的順序控制。dev(也就是 server 端)的初始序號(hào)為 seq=4264776963, syn=1。

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

第三個(gè)包,client 包確認(rèn),這里使用了相對(duì)值應(yīng)答。seq=4133153792,等于第二個(gè)包的 ack. ack=4264776964。

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

至此,三次握手完成。接下來就是發(fā)送 ping 和 pong 的數(shù)據(jù)了。

接著第四個(gè)包:

tcp 首部長度為 32B,可選長度為 12B。IP 報(bào)文的總長度為 66B,首部長度為 20B,因此 TCP 數(shù)據(jù)部分長度為 14B. seq=0xf65a ec00=4133153792。

ACK, PSH. 數(shù)據(jù)部分為 2a31 0d0a 2434 0d0a 7069 6e67 0d0a:

  1. 0x2a31         -> *1 
  2. 0x0d0a         -> \r\n 
  3. 0x2434         -> $4 
  4. 0x0d0a         -> \r\n 
  5. 0x7069 0x6e67  -> ping 
  6. 0x0d0a         -> \r\n 

dev2 向 dev 發(fā)送了 ping 數(shù)據(jù),第四個(gè)包完畢。第五個(gè)包,dev2 向 dev 發(fā)送 ack 響應(yīng)。

序列號(hào)為 0xfe33 5504=4264776964,ack 確認(rèn)號(hào)為 0xf65a ec0e=4133153806=(4133153792+14)。

第六個(gè)包,dev 向 dev2 響應(yīng) pong 消息。序列號(hào) fe33 5504,確認(rèn)號(hào) f65a ec0e,TCP 頭部可選長度為 12B,IP 數(shù)據(jù)報(bào)總長度為 59B,首部長度為 20B,因此 TCP 數(shù)據(jù)長度為 7B。

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

總結(jié)

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

以前關(guān)于 TCP 建立連接、關(guān)閉連接的過程很容易就會(huì)忘記,可能是因?yàn)橹皇撬烙浻脖沉藥讉€(gè)過程,沒有深入研究背后的原理。所以,“三次握手,四次揮手”你真的懂了嗎?

 

 

責(zé)任編輯:武曉燕 來源: 碼農(nóng)桃花源
相關(guān)推薦

2023-10-17 15:44:19

TCP四次揮手

2015-10-13 09:42:52

TCP網(wǎng)絡(luò)協(xié)議

2019-06-12 11:26:37

TCP三次握手四次揮手

2023-10-24 15:22:09

TCPUDP

2024-01-12 08:23:11

TCPACK服務(wù)器

2021-07-03 17:47:25

TCP控制協(xié)議

2021-01-29 06:11:08

TCP通信三次握手

2021-05-18 12:27:40

TCP控制協(xié)議

2020-02-17 10:10:43

TCP三次握手四次揮手

2017-09-25 21:27:07

TCP協(xié)議數(shù)據(jù)鏈

2023-03-07 08:38:23

三次握手四次揮手服務(wù)端

2021-05-28 09:08:20

TCP連接序列號(hào)

2020-06-29 14:50:47

TCP狀態(tài)ACK

2015-11-09 09:58:56

2023-10-28 09:07:57

TCP面試三次握手

2022-11-17 10:20:49

TCP三次握手四次揮手

2014-09-19 09:46:46

TCPIP

2023-11-01 08:04:08

WiresharkTCP協(xié)議

2021-08-04 08:01:28

Linux 三次握手Linux 系統(tǒng)

2020-01-09 09:31:05

三次握手四次揮手 TCP
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)