0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

深入理解TCP

科技綠洲 ? 來源:Linux開發(fā)架構(gòu)之路 ? 作者:Linux開發(fā)架構(gòu)之路 ? 2023-11-08 16:37 ? 次閱讀

當(dāng)客戶端想和服務(wù)端建立 TCP 連接的時(shí)候,首先第一個(gè)發(fā)的就是 SYN 報(bào)文,然后進(jìn)入到 SYN_SENT 狀態(tài)。

在這之后,如果客戶端遲遲收不到服務(wù)端的 SYN-ACK 報(bào)文(第二次握手),就會(huì)觸發(fā)超時(shí)重傳機(jī)制。

不同版本的操作系統(tǒng)可能超時(shí)時(shí)間不同,有的 1 秒的,也有 3 秒的,這個(gè)超時(shí)時(shí)間是寫死在內(nèi)核里的,如果想要更改則需要重新編譯內(nèi)核,比較麻煩。

當(dāng)客戶端在 1 秒后沒收到服務(wù)端的 SYN-ACK 報(bào)文后,客戶端就會(huì)重發(fā) SYN 報(bào)文,那到底重發(fā)幾次呢?

Linux 里,客戶端的 SYN 報(bào)文最大重傳次數(shù)由 tcp_syn_retries 內(nèi)核參數(shù)控制,這個(gè)參數(shù)是可以自定義的,默認(rèn)值一般是 5。

[root@localhost ~]# cat /proc/sys/net/ipv4/tcp_syn_retries
5

通常,第一次超時(shí)重傳是在 1 秒后,第二次超時(shí)重傳是在 2 秒,第三次超時(shí)重傳是在 4 秒后,第四次超時(shí)重傳是在 8 秒后,第五次是在超時(shí)重傳 16 秒后。沒錯(cuò),每次超時(shí)的時(shí)間是上一次的 2 倍。

當(dāng)?shù)谖宕纬瑫r(shí)重傳后,會(huì)繼續(xù)等待 32 秒,如果服務(wù)端仍然沒有回應(yīng) ACK,客戶端就不再發(fā)送 SYN 包,然后斷開 TCP 連接。

所以,總耗時(shí)是 1+2+4+8+16+32=63 秒,大約 1 分鐘左右。

圖片

場景復(fù)現(xiàn)

在服務(wù)端先 ban 掉客戶端的 IP iptables -I INPUT -s 客戶端 IP -j DROP,然后客戶端通過 curl 指令去訪問服務(wù)端:

圖片

圖片

可以自己設(shè)置 tcp_syn_retries:

[root@localhost ~]# echo 6 > /proc/sys/net/ipv4/tcp_syn_retries

第二次握手丟失了,會(huì)發(fā)生什么?

當(dāng)服務(wù)端收到客戶端的第一次握手后,就會(huì)回 SYN-ACK 報(bào)文給客戶端,這個(gè)就是第二次握手,此時(shí)服務(wù)端會(huì)進(jìn)入 SYN_RCVD 狀態(tài)。

第二次握手的 SYN-ACK 報(bào)文其實(shí)有兩個(gè)目的 :

  • 第二次握手里的 ACK, 是對(duì)第一次握手的確認(rèn)報(bào)文;
  • 第二次握手里的 SYN,是服務(wù)端發(fā)起建立 TCP 連接的報(bào)文;

所以,如果第二次握手丟了,就會(huì)發(fā)送比較有意思的事情,具體會(huì)怎么樣呢?

因?yàn)榈诙挝帐謭?bào)文里是包含對(duì)客戶端的第一次握手的 ACK 確認(rèn)報(bào)文,所以,如果客戶端遲遲沒有收到第二次握手,那么客戶端就覺得可能自己的 SYN 報(bào)文(第一次握手)丟失了,于是客戶端就會(huì)觸發(fā)超時(shí)重傳機(jī)制,重傳 SYN 報(bào)文。

然后,因?yàn)榈诙挝帐种邪?wù)端的 SYN 報(bào)文,所以當(dāng)客戶端收到后,需要給服務(wù)端發(fā)送 ACK 確認(rèn)報(bào)文(第三次握手),服務(wù)端才會(huì)認(rèn)為該 SYN 報(bào)文被客戶端收到了。

那么,如果第二次握手丟失了,服務(wù)端就收不到第三次握手,于是服務(wù)端這邊會(huì)觸發(fā)超時(shí)重傳機(jī)制,重傳 SYN-ACK 報(bào)文。

在 Linux 下,SYN-ACK 報(bào)文的最大重傳次數(shù)由 tcp_synack_retries 內(nèi)核參數(shù)決定,默認(rèn)值是 5。

因此,當(dāng)?shù)诙挝帐謥G失了,客戶端和服務(wù)端都會(huì)重傳:

  • 客戶端會(huì)重傳 SYN 報(bào)文,也就是第一次握手,最大重傳次數(shù)由 tcp_syn_retries 內(nèi)核參數(shù)決定;
  • 服務(wù)端會(huì)重傳 SYN-AKC 報(bào)文,也就是第二次握手,最大重傳次數(shù)由 tcp_synack_retries 內(nèi)核參數(shù)決定。

場景復(fù)現(xiàn)

這時(shí)候解除服務(wù)端 ban 掉的 客戶端的IP iptables -D INPUT -s 客戶端 IP -j DROP,轉(zhuǎn)而讓客戶端 ban 掉服務(wù)端的 IP,這樣的話,客戶端發(fā)送的 SYN 包能被服務(wù)端收到,但是服務(wù)端返回的 ACK 包會(huì)在網(wǎng)絡(luò)層就被嘎掉,無法到達(dá)傳輸層交給 TCP 解析,因此就人為的造成了第二次握手失敗的場景;

圖片

客戶端設(shè)置了防火墻,屏蔽了服務(wù)端的網(wǎng)絡(luò)包,為什么 tcpdump 還能抓到服務(wù)端的網(wǎng)絡(luò)包?

添加 iptables 限制后, tcpdump 是否能抓到包 ,這要看添加的 iptables 限制條件:

  • 如果添加的是 INPUT 規(guī)則,則可以抓得到包;
  • 如果添加的是 OUTPUT 規(guī)則,則抓不到包;

網(wǎng)絡(luò)包進(jìn)入主機(jī)后的順序如下:

  • 進(jìn)來的順序 Wire -> NIC -> tcpdump -> netfilter/iptables;
  • 出去的順序 iptables -> tcpdump -> NIC -> Wire;

第三次握手丟失了,會(huì)發(fā)生什么?

客戶端收到服務(wù)端的 SYN-ACK 報(bào)文后,就會(huì)給服務(wù)端回一個(gè) ACK 報(bào)文,也就是第三次握手,此時(shí)客戶端狀態(tài)進(jìn)入到 ESTABLISH 狀態(tài)。

因?yàn)檫@個(gè)第三次握手的 ACK 是對(duì)第二次握手的 SYN 的確認(rèn)報(bào)文,所以當(dāng)?shù)谌挝帐謥G失了,如果服務(wù)端那一方遲遲收不到這個(gè)確認(rèn)報(bào)文,就會(huì)觸發(fā)超時(shí)重傳機(jī)制,重傳 SYN-ACK 報(bào)文,直到收到第三次握手,或者達(dá)到最大重傳次數(shù)。

注意,ACK 報(bào)文是不會(huì)有重傳的,當(dāng) ACK 丟失了,就由對(duì)方重傳對(duì)應(yīng)的報(bào)文。

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
+0 `echo socket is listening!`

+0 < S 0:0(0) win 4000
+0 > S. 0:0(0) ack 1 <...>
+0 `sleep 1000000`

揮手失敗

第一次揮手丟失了,會(huì)發(fā)生什么?

當(dāng)客戶端(主動(dòng)關(guān)閉方)調(diào)用 close 函數(shù)后,就會(huì)向服務(wù)端發(fā)送 FIN 報(bào)文,試圖與服務(wù)端斷開連接,此時(shí)客戶端的連接進(jìn)入到 FIN_WAIT_1 狀態(tài)。

正常情況下,如果能及時(shí)收到服務(wù)端(被動(dòng)關(guān)閉方)的 ACK,則會(huì)很快變?yōu)?FIN_WAIT2 狀態(tài)。

如果第一次揮手丟失了,那么客戶端遲遲收不到被動(dòng)方的 ACK 的話,也就會(huì)觸發(fā)超時(shí)重傳機(jī)制,重傳 FIN 報(bào)文,重發(fā)次數(shù)由 tcp_orphan_retries 參數(shù)控制。

當(dāng)客戶端重傳 FIN 報(bào)文的次數(shù)超過 tcp_orphan_retries 后,就不再發(fā)送 FIN 報(bào)文,直接進(jìn)入到 CLOSE 狀態(tài)。

第二次揮手丟失了,會(huì)發(fā)生什么?

當(dāng)服務(wù)端收到客戶端的第一次揮手后,就會(huì)先回一個(gè) ACK 確認(rèn)報(bào)文,此時(shí)服務(wù)端的連接進(jìn)入到 CLOSE_WAIT 狀態(tài)。

在前面我們也提了,ACK 報(bào)文是不會(huì)重傳的,所以如果服務(wù)端的第二次揮手丟失了,客戶端就會(huì)觸發(fā)超時(shí)重傳機(jī)制,重傳 FIN 報(bào)文,直到收到服務(wù)端的第二次揮手,或者達(dá)到最大的重傳次數(shù)。

這里提一下,當(dāng)客戶端收到第二次揮手,也就是收到服務(wù)端發(fā)送的 ACK 報(bào)文后,客戶端就會(huì)處于 FIN_WAIT2 狀態(tài),在這個(gè)狀態(tài)需要等服務(wù)端發(fā)送第三次揮手,也就是服務(wù)端的 FIN 報(bào)文。

對(duì)于 close 函數(shù)關(guān)閉的連接,由于無法再發(fā)送和接收數(shù)據(jù),所以 FIN_WAIT2 狀態(tài)不可以持續(xù)太久,而 tcp_fin_timeout 控制了這個(gè)狀態(tài)下連接的持續(xù)時(shí)長,默認(rèn)值是 60 秒。

這意味著對(duì)于調(diào)用 close 關(guān)閉的連接,如果在 60 秒后還沒有收到 FIN 報(bào)文,客戶端(主動(dòng)關(guān)閉方)的連接就會(huì)直接關(guān)閉。

第三次揮手丟失了,會(huì)發(fā)生什么?

當(dāng)服務(wù)端(被動(dòng)關(guān)閉方)收到客戶端(主動(dòng)關(guān)閉方)的 FIN 報(bào)文后,內(nèi)核會(huì)自動(dòng)回復(fù) ACK,同時(shí)連接處于 CLOSE_WAIT 狀態(tài),顧名思義,它表示等待應(yīng)用進(jìn)程調(diào)用 close 函數(shù)關(guān)閉連接。

此時(shí),內(nèi)核是沒有權(quán)利替代進(jìn)程關(guān)閉連接,必須由進(jìn)程主動(dòng)調(diào)用 close 函數(shù)來觸發(fā)服務(wù)端發(fā)送 FIN 報(bào)文。

服務(wù)端處于 CLOSE_WAIT 狀態(tài)時(shí),調(diào)用了 close 函數(shù),內(nèi)核就會(huì)發(fā)出 FIN 報(bào)文,同時(shí)連接進(jìn)入 LAST_ACK 狀態(tài),等待客戶端返回 ACK 來確認(rèn)連接關(guān)閉。

如果遲遲收不到這個(gè) ACK,服務(wù)端就會(huì)重發(fā) FIN 報(bào)文,重發(fā)次數(shù)仍然由 tcp_orphan_retries 參數(shù)控制,這與客戶端重發(fā) FIN 報(bào)文的重傳次數(shù)控制方式是一樣的。

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
+0 `echo socket is listening!`

+0 < S 0:0(0) win 4000
+0 > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 1000
+0 accept(3, ..., ...) = 4
+0 `echo connection established!`

+0 < F. 1:1(0) ack 1 win 1000
+0 > . 1:1(0) ack 2
+0.1 close(4)=0
+0 > F. 1:1(0) ack 2 <...>

+0 `sleep 1000000`

圖片

第四次揮手丟失了,會(huì)發(fā)生什么?

當(dāng)客戶端收到服務(wù)端的第三次揮手的 FIN 報(bào)文后,就會(huì)回 ACK 報(bào)文,也就是第四次揮手,此時(shí)客戶端連接進(jìn)入 TIME_WAIT 狀態(tài)。

在 Linux 系統(tǒng),TIME_WAIT 狀態(tài)會(huì)持續(xù) 60 秒后才會(huì)進(jìn)入關(guān)閉狀態(tài)。

然后,服務(wù)端(被動(dòng)關(guān)閉方)沒有收到 ACK 報(bào)文前,還是處于 LAST_ACK 狀態(tài)。

如果第四次揮手的 ACK 報(bào)文沒有到達(dá)服務(wù)端,服務(wù)端就會(huì)重發(fā) FIN 報(bào)文,重發(fā)次數(shù)仍然由前面介紹過的 tcp_orphan_retries 參數(shù)控制。

為什么是三次握手?

現(xiàn)在耳熟能詳?shù)?TCP 連接就是三次握手,四次揮手,那么你有想過 為什么是三次握手,而不是兩次或者四次呢?

相信比較平?;卮鸬氖牵骸耙?yàn)槿挝帐植拍鼙WC雙方具有接收和發(fā)送的能力”。這樣的回答是沒問題的,但是這回答是片面的,并沒有說出主要的原因。

在前面我們知道了什么是 TCP 連接:用于保證可靠性和流量控制維護(hù)的某些狀態(tài)信息,這些信息的組合,包括 Socket、序列號(hào)和窗口大小稱為連接。

所以,重要的是為什么三次握手才可以初始化 Socket、序列號(hào)和窗口大小并建立 TCP 連接。

接下來以三個(gè)方面分析三次握手的原因:

  • 三次握手才可以阻止重復(fù)歷史連接的初始化(主要原因);
  • 三次握手才可以同步雙方的初始序列號(hào);
  • 三次握手才可以避免資源浪費(fèi);

原因一:避免歷史連接

RFC 793 指出的 TCP 連接使用三次握手的首要原因:

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

簡單來說,三次握手的首要原因是為了防止舊的重復(fù)連接初始化造成混亂。

網(wǎng)絡(luò)環(huán)境是錯(cuò)綜復(fù)雜的,往往并不是如我們期望的一樣,先發(fā)送的數(shù)據(jù)包,就先到達(dá)目標(biāo)主機(jī);可能會(huì)由于網(wǎng)絡(luò)擁堵等亂七八糟的原因,會(huì)使得舊的數(shù)據(jù)包,先到達(dá)目標(biāo)主機(jī)。那么這種情況下 TCP 三次握手是如何避免的呢?

圖片

客戶端連續(xù)發(fā)送多次 SYN 建立連接的報(bào)文,在網(wǎng)絡(luò)擁堵情況下:

  • 一個(gè)「舊 SYN 報(bào)文」比「最新的 SYN 」 報(bào)文早到達(dá)了服務(wù)端;
  • 那么此時(shí)服務(wù)端就會(huì)回一個(gè) SYN + ACK 報(bào)文給客戶端;
  • 客戶端收到后可以根據(jù)自身的上下文,判斷這是一個(gè)歷史連接(序列號(hào)過期或超時(shí)),那么客戶端就會(huì)發(fā)送 RST 報(bào)文給服務(wù)端,表示中止這一次連接。

如果是兩次握手連接,就不能判斷當(dāng)前連接是否是歷史連接,三次握手則可以在客戶端(發(fā)送方)準(zhǔn)備發(fā)送第三次報(bào)文時(shí),客戶端因有足夠的上下文來判斷當(dāng)前連接是否是歷史連接:

  • 如果是歷史連接(序列號(hào)過期或超時(shí)),則第三次握手發(fā)送的報(bào)文是 RST 報(bào)文,以此中止歷史連接;
  • 如果不是歷史連接,則第三次發(fā)送的報(bào)文是 ACK 報(bào)文,通信雙方就會(huì)成功建立連接;

所以,TCP 使用三次握手建立連接的最主要原因是防止歷史連接初始化了連接。

原因二:同步雙方初始序列號(hào)

TCP 協(xié)議的通信雙方, 都必須維護(hù)一個(gè)「序列號(hào)」, 序列號(hào)是可靠傳輸?shù)囊粋€(gè)關(guān)鍵因素,它的作用:

  • 接收方可以去除重復(fù)的數(shù)據(jù);
  • 接收方可以根據(jù)數(shù)據(jù)包的序列號(hào)按序接收;
  • 可以標(biāo)識(shí)發(fā)送出去的數(shù)據(jù)包中, 哪些是已經(jīng)被對(duì)方收到的;

可見,序列號(hào)在 TCP 連接中占據(jù)著非常重要的作用,所以當(dāng)客戶端發(fā)送攜帶「初始序列號(hào)」的 SYN 報(bào)文的時(shí)候,需要服務(wù)端回一個(gè) ACK 應(yīng)答報(bào)文,表示客戶端的 SYN 報(bào)文已被服務(wù)端成功接收,那當(dāng)服務(wù)端發(fā)送「初始序列號(hào)」給客戶端的時(shí)候,依然也要得到客戶端的應(yīng)答回應(yīng),這樣一來一回,才能確保雙方的初始序列號(hào)能被可靠的同步。

四次握手與三次握手:

圖片

四次握手其實(shí)也能夠可靠的同步雙方的初始化序號(hào),但由于第二步和第三步可以優(yōu)化成一步,所以就成了「三次握手」。

而兩次握手只保證了一方的初始序列號(hào)能被對(duì)方成功接收,沒辦法保證雙方的初始序列號(hào)都能被確認(rèn)接收。

原因三:避免資源浪費(fèi)

如果只有「兩次握手」,當(dāng)客戶端的 SYN 請(qǐng)求連接在網(wǎng)絡(luò)中阻塞,客戶端沒有接收到 ACK 報(bào)文,就會(huì)重新發(fā)送 SYN ,由于沒有第三次握手,服務(wù)器不清楚客戶端是否收到了自己發(fā)送的建立連接的 ACK 確認(rèn)信號(hào),所以每收到一個(gè) SYN 就只能先主動(dòng)建立一個(gè)連接,這會(huì)造成什么情況呢?

如果客戶端的 SYN 阻塞了,重復(fù)發(fā)送多次 SYN 報(bào)文,那么服務(wù)器在收到請(qǐng)求后就會(huì)建立多個(gè)冗余的無效鏈接,造成不必要的資源浪費(fèi)。

兩次握手會(huì)造成資源浪費(fèi):

圖片

即兩次握手會(huì)造成消息滯留情況下,服務(wù)器重復(fù)接受無用的連接請(qǐng)求 SYN 報(bào)文,而造成重復(fù)分配資源。

小結(jié)

TCP 建立連接時(shí),通過三次握手能:

  • 防止歷史連接的建立,能減少雙方不必要的資源開銷;
  • 能幫助雙方同步初始化序列號(hào),序列號(hào)能夠保證數(shù)據(jù)包不重復(fù)、不丟棄和按序傳輸;

不使用「兩次握手」和「四次握手」的原因:

  • 「兩次握手」:無法防止歷史連接的建立,會(huì)造成雙方資源的浪費(fèi),也無法可靠的同步雙方序列號(hào);
  • 「四次握手」:三次握手就已經(jīng)理論上最少可靠連接建立,所以不需要使用更多的通信次數(shù);

如何避免 SYN 攻擊?

什么是 SYN 攻擊?

在了解如何避免 SYN 攻擊前,我們得先了解什么是 SYN 攻擊;

我們都知道 TCP 連接建立是需要三次握手,假設(shè)攻擊者短時(shí)間偽造不同 IP 地址的 SYN 報(bào)文,服務(wù)端每接收到一個(gè) SYN 報(bào)文,就進(jìn)入SYN_RCVD 狀態(tài),但服務(wù)端發(fā)送出去的 SYN + ACK 報(bào)文,無法得到未知 IP 主機(jī)的 ACK 應(yīng)答,久而久之就會(huì)占滿服務(wù)端的 SYN 接收隊(duì)列(未連接隊(duì)列),使得服務(wù)器不能為正常用戶服務(wù)。

也就是上面曾講述的 第三次握手失敗;

圖片

常用的工具有 LOIC,Hping3 等,演示攻擊如下:

圖片

因?yàn)樘摂M機(jī)的配置低,所以很容易就實(shí)現(xiàn) CPU 爆滿的狀況了,這時(shí)候服務(wù)器就很難響應(yīng)其他服務(wù)請(qǐng)求,更嚴(yán)重的可能會(huì)直接宕機(jī);

圖片

避免 SYN 攻擊方式一

第一種解決方式是通過修改 Linux 內(nèi)核參數(shù),控制隊(duì)列大小和當(dāng)隊(duì)列滿時(shí)應(yīng)做什么處理。

當(dāng)網(wǎng)卡接收數(shù)據(jù)包的速度大于內(nèi)核處理的速度時(shí),會(huì)有一個(gè)隊(duì)列保存這些數(shù)據(jù)包。

  • 控制該隊(duì)列的最大值:net.core.netdev_max_backlog;
  • SYN_RCVD 狀態(tài)連接的最大個(gè)數(shù):net.ipv4.tcp_max_syn_backlog;
  • 超出處理能時(shí),對(duì)新的 SYN 直接回報(bào) RST,丟棄連接:net.ipv4.tcp_abort_on_overflow;

避免 SYN 攻擊方式二

我們先來看下 Linux 內(nèi)核的 SYN (未完成連接建立)隊(duì)列與 Accpet (已完成連接建立)隊(duì)列是如何工作的:

1、正常流程;

圖片

  • 當(dāng)服務(wù)端接收到客戶端的 SYN 報(bào)文時(shí),會(huì)將其加入到內(nèi)核的「 SYN 隊(duì)列」;
  • 接著發(fā)送 SYN + ACK 給客戶端,等待客戶端回應(yīng) ACK 報(bào)文;
  • 服務(wù)端接收到 ACK 報(bào)文后,從「 SYN 隊(duì)列」移除放入到「 Accept 隊(duì)列」;
  • 應(yīng)用通過調(diào)用 accpet() socket 接口,從「 Accept 隊(duì)列」取出連接。

2、應(yīng)用程序過慢;

圖片

如果應(yīng)用程序過慢時(shí),就會(huì)導(dǎo)致「 Accept 隊(duì)列」被占滿。

3、受到 SYN 攻擊;

圖片

如果不斷受到 SYN 攻擊,就會(huì)導(dǎo)致「 SYN 隊(duì)列」被占滿。

tcp_syncookies 的方式可以應(yīng)對(duì) SYN 攻擊的方法:net.ipv4.tcp_syncookies = 1

圖片

  • 當(dāng) 「 SYN 隊(duì)列」?jié)M之后,后續(xù)服務(wù)器收到 SYN 包,不進(jìn)入「 SYN 隊(duì)列」;
  • 計(jì)算出一個(gè) cookie 值,再以 SYN + ACK 中的「序列號(hào)」返回客戶端;
  • 服務(wù)端接收到客戶端的應(yīng)答報(bào)文時(shí),服務(wù)器會(huì)檢查這個(gè) ACK 包的合法性,如果合法,直接放入到「 Accept 隊(duì)列」;
  • 最后應(yīng)用通過調(diào)用 accpet() socket 接口,從「 Accept 隊(duì)列」取出的連接。

MTU 與 MSS 那些事兒

最大報(bào)文段長度 MSS 與 最大傳輸單元 MTU 均是協(xié)議用來定義最大長度的。不同的是,MTU 應(yīng)用于 OSI 模型的第二層數(shù)據(jù)鏈接層,并無具體針對(duì)的協(xié)議,限制了數(shù)據(jù)鏈接層上可以傳輸?shù)臄?shù)據(jù)包的大小,也因此限制了上層(網(wǎng)絡(luò)層)的數(shù)據(jù)包大??;MSS 針對(duì)的是 OSI 模型里的第四層傳輸層的 TCP 協(xié)議,因?yàn)?MSS 應(yīng)用的協(xié)議在數(shù)據(jù)鏈接層的上層,所以 MSS 會(huì)受到 MTU 的限制。

  • MTU:Maximum Transmission Unit,最大傳輸單元,由硬件規(guī)定,如以太網(wǎng)的 MTU 為1500字節(jié)。
  • MSS:Maximum Segment Size,最大分段大小,是 TCP 數(shù)據(jù)包每次傳輸?shù)淖畲髷?shù)據(jù)分段大小,一般由發(fā)送端向?qū)Χ?TCP 通知,對(duì)端在每個(gè)分節(jié)中能發(fā)送的最大 TCP 數(shù)據(jù)。MSS 值為 MTU 值減去IPv4 Header(20 Byte)和 TCP header(20 Byte)得到。
  • 分片:Fragmentation,若 IP 數(shù)據(jù)報(bào)大小超過相應(yīng)鏈路的 MTU 的時(shí)候,IPV4 和 IPV6 都執(zhí)行分片操作,各片段到達(dá)目的地前通常不會(huì)被重組 (re-assembling)。IPV4 主機(jī)對(duì)其產(chǎn)生的數(shù)據(jù)報(bào)執(zhí)行分片,IPV4 路由器對(duì)其轉(zhuǎn)發(fā)的數(shù)據(jù)也執(zhí)行分片,然而 IPV6 只在數(shù)據(jù)產(chǎn)生的主機(jī)執(zhí)行分片,IPV6 路由器對(duì)其轉(zhuǎn)發(fā)的數(shù)據(jù)不執(zhí)行分片。

那么問題來了,為什么由 Wireshark 抓到的數(shù)據(jù)包的MSS = 1460,但卻在Len = 1448的時(shí)候就進(jìn)行分包了呢?如下圖所示:

圖片

既然在握手階段就協(xié)商了 MSS = 1460,那為什么 TCP 的最大數(shù)據(jù)段長度卻只有 1448 bytes 呢?

原來在實(shí)際場景中,TCP 包頭中會(huì)帶有12字節(jié)的選項(xiàng),時(shí)間戳(Timestamps),這樣,單個(gè) TCP 包實(shí)際傳輸?shù)淖畲罅烤涂s減為1448字節(jié)了,如下所示:

圖片

那 MTU = MSS + IP 頭長度 + TCP 頭長度,一般 IP 頭長度和 TCP 頭長度都為20,即MTU = MSS + 20 +20 = 1500,但是呢,從下圖中不難發(fā)現(xiàn),一個(gè) TCP 包的總長度 Length 卻大于 MTU,這是為什么呢?

圖片

這是根據(jù)以太網(wǎng)幀結(jié)構(gòu)所決定的,至于如何判斷是否為以太網(wǎng),可以根據(jù)物理層中的[Protocols in frame: eth:ethertype:ip:tcp]判定,這表示幀內(nèi)封裝的協(xié)議層次結(jié)構(gòu);

圖片

在不選擇填充 802.1Q 標(biāo)簽,負(fù)載拉滿(即為 MTU 值)的前提下,以太網(wǎng)的最大幀大小應(yīng)該是 前導(dǎo)碼+幀開始符+MAC目標(biāo)地址+MAC源地址+以太類型+負(fù)載+冗余校驗(yàn) = 7+1+6+6+2+1500+4 = 1526 字節(jié),那為什么 Wireshark 抓來的數(shù)據(jù)包的最大幀卻只有1514字節(jié)呢?

原來是因?yàn)楫?dāng)數(shù)據(jù)幀到達(dá)網(wǎng)卡時(shí),在物理層上網(wǎng)卡要先去掉前導(dǎo)碼和幀開始定界符,然后對(duì)幀進(jìn)行 CRC 校驗(yàn):如果幀校驗(yàn)和錯(cuò)誤,就丟棄此幀;如果幀校驗(yàn)和正確,就判斷該幀的 MAC 目的地址是否符合自己的接收條件,如果符合,就將此幀交付 設(shè)備驅(qū)動(dòng)程序 做進(jìn)一步處理。這時(shí) Wireshark 才能抓到數(shù)據(jù),因此,Wireshark 抓到的是去掉前導(dǎo)碼、幀開始分界符、CRC校驗(yàn)之外的數(shù)據(jù),其最大值是 6+6+2+1500=1514 字節(jié);

TIME_WAIT 的巧妙設(shè)計(jì)

為什么 TIME_WAIT 等待的時(shí)間是 2MSL?

MSL (Maximum Segment Lifetime),報(bào)文最大生存時(shí)間,它是任何報(bào)文在網(wǎng)絡(luò)上存在的最長時(shí)間,超過這個(gè)時(shí)間報(bào)文將被丟棄。因?yàn)?TCP 報(bào)文基于是 IP 協(xié)議的,而 IP 頭中有一個(gè) TTL 字段,是 IP 數(shù)據(jù)報(bào)可以經(jīng)過的最大路由數(shù),每經(jīng)過一個(gè)處理他的路由器此值就減 1,當(dāng)此值為 0 則數(shù)據(jù)報(bào)將被丟棄,同時(shí)發(fā)送 ICMP 報(bào)文通知源主機(jī)。

MSL 與 TTL 的區(qū)別: MSL 的單位是時(shí)間,而 TTL 是經(jīng)過路由跳數(shù)。所以 MSL 應(yīng)該要大于等于 TTL 消耗為 0 的時(shí)間,以確保報(bào)文已被自然消亡。

TIME_WAIT 等待 2 倍的 MSL,比較合理的解釋是: 網(wǎng)絡(luò)中可能存在來自發(fā)送方的數(shù)據(jù)包,當(dāng)這些發(fā)送方的數(shù)據(jù)包被接收方處理后又會(huì)向?qū)Ψ桨l(fā)送響應(yīng),所以一來一回需要等待 2 倍的時(shí)間。

比如被動(dòng)關(guān)閉方?jīng)]有收到斷開連接的最后的 ACK 報(bào)文,就會(huì)觸發(fā)超時(shí)重發(fā) FIN 報(bào)文,另一方接收到 FIN 后,會(huì)重發(fā) ACK 給被動(dòng)關(guān)閉方, 一來一去正好 2 個(gè) MSL。

2MSL 的時(shí)間是從客戶端接收到 FIN 后發(fā)送 ACK 開始計(jì)時(shí)的。如果在 TIME-WAIT 時(shí)間內(nèi),因?yàn)榭蛻舳说?ACK 沒有傳輸?shù)椒?wù)端,客戶端又接收到了服務(wù)端重發(fā)的 FIN 報(bào)文,那么 2MSL 時(shí)間將重新計(jì)時(shí)。

在 Linux 系統(tǒng)里 2MSL 默認(rèn)是 60 秒,那么一個(gè) MSL 也就是 30 秒。Linux 系統(tǒng)停留在 TIME_WAIT 的時(shí)間為固定的 60 秒。

其定義在 Linux 內(nèi)核代碼里的名稱為 TCP_TIMEWAIT_LEN:

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT state, about 60 seconds */

如果要修改 TIME_WAIT 的時(shí)間長度,只能修改 Linux 內(nèi)核代碼里 TCP_TIMEWAIT_LEN 的值,并重新編譯 Linux 內(nèi)核。

為什么需要 TIME_WAIT 狀態(tài)?

主動(dòng)發(fā)起關(guān)閉連接的一方,才會(huì)有 TIME-WAIT 狀態(tài)。

需要 TIME-WAIT 狀態(tài),主要是兩個(gè)原因:

  • 防止具有相同「四元組」的「舊」數(shù)據(jù)包被收到;
  • 保證「被動(dòng)關(guān)閉連接」的一方能被正確的關(guān)閉,即保證最后的 ACK 能讓被動(dòng)關(guān)閉方接收,從而幫助其正常關(guān)閉;

原因一:防止舊連接的數(shù)據(jù)包

假設(shè)TIME-WAIT沒有等待時(shí)間或時(shí)間過短,被延遲的數(shù)據(jù)包抵達(dá)后會(huì)發(fā)生什么呢?

圖片

  • 如上圖黃色框框服務(wù)端在關(guān)閉連接之前發(fā)送的 SEQ = 301 報(bào)文,被網(wǎng)絡(luò)延遲了。
  • 這時(shí)有相同端口的 TCP 連接被復(fù)用后,被延遲的 SEQ = 301 抵達(dá)了客戶端,那么客戶端是有可能正常接收這個(gè)過期的報(bào)文,這就會(huì)產(chǎn)生數(shù)據(jù)錯(cuò)亂等嚴(yán)重的問題。

所以,TCP 就設(shè)計(jì)出了這么一個(gè)機(jī)制,經(jīng)過 2MSL 這個(gè)時(shí)間,足以讓兩個(gè)方向上的數(shù)據(jù)包都被丟棄,使得原來連接的數(shù)據(jù)包在網(wǎng)絡(luò)中都自然消失,再出現(xiàn)的數(shù)據(jù)包一定都是新建立連接所產(chǎn)生的。

原因二:保證連接正確關(guān)閉

在 RFC 793 指出 TIME-WAIT 另一個(gè)重要的作用是:

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 作用是等待足夠的時(shí)間以確保最后的 ACK 能讓被動(dòng)關(guān)閉方接收,從而幫助其正常關(guān)閉。

假設(shè) TIME-WAIT 沒有等待時(shí)間或時(shí)間過短,斷開連接會(huì)造成什么問題呢?

圖片

  • 如上圖紅色框框客戶端四次揮手的最后一個(gè) ACK 報(bào)文如果在網(wǎng)絡(luò)中被丟失了,此時(shí)如果客戶端 TIME-WAIT 過短或沒有,則就直接進(jìn)入了 CLOSED 狀態(tài)了,那么服務(wù)端則會(huì)一直處在 LASE_ACK 狀態(tài)。
  • 當(dāng)客戶端發(fā)起建立連接的 SYN 請(qǐng)求報(bào)文后,服務(wù)端會(huì)發(fā)送 RST 報(bào)文給客戶端,連接建立的過程就會(huì)被終止。

如果 TIME-WAIT 等待足夠長的情況就會(huì)遇到兩種情況:

  • 服務(wù)端正常收到四次揮手的最后一個(gè) ACK 報(bào)文,則服務(wù)端正常關(guān)閉連接。
  • 服務(wù)端沒有收到四次揮手的最后一個(gè) ACK 報(bào)文時(shí),則會(huì)重發(fā) FIN 關(guān)閉連接報(bào)文并等待新的 ACK 報(bào)文。

所以客戶端在 TIME-WAIT 狀態(tài)等待 2MSL 時(shí)間后,就可以保證雙方的連接都可以正常的關(guān)閉。

TIME_WAIT 過長有什么危害?

如果服務(wù)器有處于 TIME-WAIT 狀態(tài)的 TCP,則說明是由服務(wù)器方主動(dòng)發(fā)起的斷開請(qǐng)求。

過多的 TIME-WAIT 狀態(tài)主要的危害有兩種:

  • 第一是內(nèi)存資源占用;
  • 第二是對(duì)端口資源的占用,一個(gè) TCP 連接至少消耗一個(gè)本地端口;

第二個(gè)危害是會(huì)造成嚴(yán)重的后果的,要知道,端口資源也是有限的,一般可以開啟的端口為 32768~61000,也可以通過參數(shù)設(shè)置指定 net.ipv4.ip_local_port_range,如果發(fā)起連接一方的 TIME_WAIT 狀態(tài)過多,占滿了所有端口資源,則會(huì)導(dǎo)致無法創(chuàng)建新連接。

客戶端受端口資源限制:

  • 客戶端 TIME_WAIT 過多,就會(huì)導(dǎo)致端口資源被占用,因?yàn)槎丝诰?5536個(gè),被占滿就會(huì)導(dǎo)致無法創(chuàng)建新的連接。

服務(wù)端受系統(tǒng)資源限制:

  • 由于一個(gè)四元組表示 TCP 連接,理論上服務(wù)端可以建立很多連接,服務(wù)端確實(shí)只監(jiān)聽一個(gè)端口 但是會(huì)把連接扔給處理線程,所以理論上監(jiān)聽的端口可以繼續(xù)監(jiān)聽。但是線程池處理不了那么多一直不斷的連接了。所以當(dāng)服務(wù)端出現(xiàn)大量 TIME_WAIT 時(shí),系統(tǒng)資源被占滿時(shí),會(huì)導(dǎo)致處理不過來新的連接。

如何優(yōu)化 TIME_WAIT?

這里給出優(yōu)化 TIME-WAIT 的幾個(gè)方式,都是有利有弊:

方式一:net.ipv4.tcp_tw_reuse 和 tcp_timestamps

如下的 Linux 內(nèi)核參數(shù)開啟后,則可以復(fù)用處于 TIME_WAIT 的 socket 為新的連接所用。

有一點(diǎn)需要注意的是,tcp_tw_reuse 功能只能用于客戶端(連接發(fā)起方),因?yàn)殚_啟了該功能,在調(diào)用 connect() 函數(shù)時(shí),內(nèi)核會(huì)隨機(jī)找一個(gè) time_wait 狀態(tài)超過 1 秒的連接給新的連接復(fù)用。

net.ipv4.tcp_tw_reuse = 1

使用這個(gè)選項(xiàng),還有一個(gè)前提,需要打開對(duì) TCP 時(shí)間戳的支持,即 net.ipv4.tcp_timestamps=1(默認(rèn)即為 1),這個(gè)時(shí)間戳的字段是在 TCP 頭部的「選項(xiàng)」里,用于記錄 TCP 發(fā)送方的當(dāng)前時(shí)間戳和從對(duì)端接收到的最新時(shí)間戳。

由于引入了時(shí)間戳,我們在前面提到的 2MSL 問題就不復(fù)存在了,因?yàn)橹貜?fù)的數(shù)據(jù)包會(huì)因?yàn)闀r(shí)間戳過期被自然丟棄。

方式二:net.ipv4.tcp_max_tw_buckets

這個(gè)值默認(rèn)為 18000,當(dāng)系統(tǒng)中處于 TIME_WAIT 的連接一旦超過這個(gè)值時(shí),系統(tǒng)就會(huì)將后面的 TIME_WAIT 連接狀態(tài)重置。

這個(gè)方法過于暴力,而且治標(biāo)不治本,帶來的問題遠(yuǎn)比解決的問題多,不推薦使用。

方式三:程序中使用 SO_LINGER

我們可以通過設(shè)置 socket 選項(xiàng),來設(shè)置調(diào)用 close 關(guān)閉連接行為。

struct linger so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger,sizeof(so_linger));

如果 l_onoff 為非 0, 且 l_linger 值為 0,那么調(diào)用 close 后,會(huì)立該發(fā)送一個(gè) RST 標(biāo)志給對(duì)端,該 TCP 連接將跳過四次揮手,也就跳過了 TIME_WAIT 狀態(tài),直接關(guān)閉。

但這為跨越 TIME_WAIT 狀態(tài)提供了一個(gè)可能,不過是一個(gè)非常危險(xiǎn)的行為,不值得提倡。

如果已經(jīng)建立了連接,但是客戶端突然出現(xiàn)故障了怎么辦?

TCP 有一個(gè)機(jī)制是保活機(jī)制。這個(gè)機(jī)制的原理是這樣的:

定義一個(gè)時(shí)間段,在這個(gè)時(shí)間段內(nèi),如果沒有任何連接相關(guān)的活動(dòng),TCP 保活機(jī)制會(huì)開始作用,每隔一個(gè)時(shí)間間隔,發(fā)送一個(gè)探測報(bào)文,該探測報(bào)文包含的數(shù)據(jù)非常少,如果連續(xù)幾個(gè)探測報(bào)文都沒有得到響應(yīng),則認(rèn)為當(dāng)前的 TCP 連接已經(jīng)死亡,系統(tǒng)內(nèi)核將錯(cuò)誤信息通知給上層應(yīng)用程序。

在 Linux 內(nèi)核可以有對(duì)應(yīng)的參數(shù)可以設(shè)置?;顣r(shí)間、?;钐綔y的次數(shù)、?;钐綔y的時(shí)間間隔,以下都為默認(rèn)值:

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
# 表示?;顣r(shí)間是 7200 秒(2小時(shí)),也就 2 小時(shí)內(nèi)如果沒有任何連接相關(guān)的活動(dòng),則會(huì)啟動(dòng)?;顧C(jī)制
tcp_keepalive_time=7200
# 表示每次檢測間隔 75 秒
tcp_keepalive_intvl=75
# 表示檢測 9 次無響應(yīng),認(rèn)為對(duì)方是不可達(dá)的,從而中斷本次的連接。
tcp_keepalive_probes=9:

也就是說在 Linux 系統(tǒng)中,最少需要經(jīng)過 2 小時(shí) 11 分 15 秒才可以發(fā)現(xiàn)一個(gè)「死亡」連接。

這個(gè)時(shí)間是有點(diǎn)長的,我們也可以根據(jù)實(shí)際的需求,對(duì)以上的?;钕嚓P(guān)的參數(shù)進(jìn)行設(shè)置。

如果開啟了 TCP ?;睿枰紤]以下幾種情況:

第一種,對(duì)端程序是正常工作的。當(dāng) TCP ?;畹奶綔y報(bào)文發(fā)送給對(duì)端, 對(duì)端會(huì)正常響應(yīng),這樣 TCP 保活時(shí)間會(huì)被重置,等待下一個(gè) TCP ?;顣r(shí)間的到來。

第二種,對(duì)端程序崩潰并重啟。當(dāng) TCP ?;畹奶綔y報(bào)文發(fā)送給對(duì)端后,對(duì)端是可以響應(yīng)的,但由于沒有該連接的有效信息,會(huì)產(chǎn)生一個(gè) RST 報(bào)文,這樣很快就會(huì)發(fā)現(xiàn) TCP 連接已經(jīng)被重置。

第三種,是對(duì)端程序崩潰,或?qū)Χ擞捎谄渌驅(qū)е聢?bào)文不可達(dá)。當(dāng) TCP ?;畹奶綔y報(bào)文發(fā)送給對(duì)端后,石沉大海,沒有響應(yīng),連續(xù)幾次,達(dá)到?;钐綔y次數(shù)后,TCP 會(huì)報(bào)告該 TCP 連接已經(jīng)死亡。

初始序列號(hào) ISN為什么不同?

主要原因是為了防止歷史報(bào)文被下一個(gè)相同四元組的連接接收。

如果一個(gè)已經(jīng)失效的連接被重用了,但是該舊連接的歷史報(bào)文還殘留在網(wǎng)絡(luò)中,如果序列號(hào)相同,那么就無法分辨出該報(bào)文是不是歷史報(bào)文,如果歷史報(bào)文被新的連接接收了,則會(huì)產(chǎn)生數(shù)據(jù)錯(cuò)亂。所以,每次建立連接前重新初始化一個(gè)序列號(hào)主要是為了通信雙方能夠根據(jù)序號(hào)將不屬于本連接的報(bào)文段丟棄。

另一方面是為了安全性,防止黑客偽造的相同序列號(hào)的 TCP 報(bào)文被對(duì)方接收。

初始序列號(hào) ISN 是如何隨機(jī)產(chǎn)生的?

起始 ISN 是基于時(shí)鐘的,每 4 毫秒 + 1,轉(zhuǎn)一圈要 4.55 個(gè)小時(shí)。

RFC1948 中提出了一個(gè)較好的初始化序列號(hào) ISN 隨機(jī)生成算法

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

M 是一個(gè)計(jì)時(shí)器,這個(gè)計(jì)時(shí)器每隔 4 毫秒加 1。

F 是一個(gè) Hash 算法,根據(jù)源 IP、目的 IP、源端口、目的端口生成一個(gè)隨機(jī)數(shù)值。要保證 Hash 算法不能被外部輕易推算得出,用 MD5 算法是一個(gè)比較好的選擇。

TIME_WAIT 狀態(tài)不是會(huì)持續(xù) 2 MSL 時(shí)長,歷史報(bào)文不是早就在網(wǎng)絡(luò)中消失了嗎?

是的,如果能正常四次揮手,由于 TIME_WAIT 狀態(tài)會(huì)持續(xù) 2 MSL 時(shí)長,歷史報(bào)文會(huì)在下一個(gè)連接之前就會(huì)自然消失。

但是來了,我們并不能保證每次連接都能通過四次揮手來正常關(guān)閉連接。

假設(shè)每次建立連接,客戶端和服務(wù)端的初始化序列號(hào)都是從 0 開始:

圖片

過程如下:

  • 客戶端和服務(wù)端建立一個(gè) TCP 連接,在客戶端發(fā)送數(shù)據(jù)包被網(wǎng)絡(luò)阻塞了,而此時(shí)服務(wù)端的進(jìn)程重啟了,于是就會(huì)發(fā)送 RST 報(bào)文來斷開連接。
  • 緊接著,客戶端又與服務(wù)端建立了與上一個(gè)連接相同四元組的連接;
  • 在新連接建立完成后,上一個(gè)連接中被網(wǎng)絡(luò)阻塞的數(shù)據(jù)包正好抵達(dá)了服務(wù)端,剛好該數(shù)據(jù)包的序列號(hào)正好是在服務(wù)端的接收窗口內(nèi),所以該數(shù)據(jù)包會(huì)被服務(wù)端正常接收,就會(huì)造成數(shù)據(jù)錯(cuò)亂。

可以看到,如果每次建立連接,客戶端和服務(wù)端的初始化序列號(hào)都是一樣的話,很容易出現(xiàn)歷史報(bào)文被下一個(gè)相同四元組的連接接收的問題。

客戶端和服務(wù)端的初始化序列號(hào)不一樣不是也會(huì)發(fā)生這樣的事情嗎?

是的,即使客戶端和服務(wù)端的初始化序列號(hào)不一樣,也會(huì)存在收到歷史報(bào)文的可能。

但是我們要清楚一點(diǎn),歷史報(bào)文能否被對(duì)方接收,還要看該歷史報(bào)文的序列號(hào)是否正好在對(duì)方接收窗口內(nèi),如果不在就會(huì)丟棄,如果在才會(huì)接收。

如果每次建立連接客戶端和服務(wù)端的初始化序列號(hào)都「不一樣」,就有大概率因?yàn)闅v史報(bào)文的序列號(hào)「不在」對(duì)方接收窗口,從而很大程度上避免了歷史報(bào)文,比如下圖:

圖片

相反,如果每次建立連接客戶端和服務(wù)端的初始化序列號(hào)都「一樣」,就有大概率遇到歷史報(bào)文的序列號(hào)剛「好在」對(duì)方的接收窗口內(nèi),從而導(dǎo)致歷史報(bào)文被新連接成功接收。

所以,每次初始化序列號(hào)不一樣能夠很大程度上避免歷史報(bào)文被下一個(gè)相同四元組的連接接收,注意是很大程度上,并不是完全避免了。

客戶端和服務(wù)端的初始化序列號(hào)不一樣不是也會(huì)發(fā)生這樣的事情嗎?

是的,但是也不是完全避免了。

為了能更好的理解這個(gè)原因,我們先來了解序列號(hào)(SEQ)和初始序列號(hào)(ISN)。

  • 序列號(hào),是 TCP 一個(gè)頭部字段,標(biāo)識(shí)了 TCP 發(fā)送端到 TCP 接收端的數(shù)據(jù)流的一個(gè)字節(jié),因?yàn)?TCP 是面向字節(jié)流的可靠協(xié)議,為了保證消息的順序性和可靠性,TCP 為每個(gè)傳輸方向上的每個(gè)字節(jié)都賦予了一個(gè)編號(hào),以便于傳輸成功后確認(rèn)、丟失后重傳以及在接收端保證不會(huì)亂序。序列號(hào)是一個(gè) 32 位的無符號(hào)數(shù),因此在到達(dá) 4G 之后再循環(huán)回到 0。
  • 初始序列號(hào),在 TCP 建立連接的時(shí)候,客戶端和服務(wù)端都會(huì)各自生成一個(gè)初始序列號(hào),它是基于時(shí)鐘生成的一個(gè)隨機(jī)數(shù),來保證每個(gè)連接都擁有不同的初始序列號(hào)。初始化序列號(hào)可被視為一個(gè) 32 位的計(jì)數(shù)器,該計(jì)數(shù)器的數(shù)值每 4 微秒加 1,循環(huán)一次需要 4.55 小時(shí)。

圖片

通過前面我們知道,序列號(hào)和初始化序列號(hào)并不是無限遞增的,會(huì)發(fā)生回繞為初始值的情況,這意味著無法根據(jù)序列號(hào)來判斷新老數(shù)據(jù)。

不要以為序列號(hào)的上限值是 4GB,就以為很大,很難發(fā)生回繞。在一個(gè)速度足夠快的網(wǎng)絡(luò)中傳輸大量數(shù)據(jù)時(shí),序列號(hào)的回繞時(shí)間就會(huì)變短。如果序列號(hào)回繞的時(shí)間極短,我們就會(huì)再次面臨之前延遲的報(bào)文抵達(dá)后序列號(hào)依然有效的問題。

為了解決這個(gè)問題,就需要有 TCP 時(shí)間戳。tcp_timestamps 參數(shù)是默認(rèn)開啟的,開啟了 tcp_timestamps 參數(shù),TCP 頭部就會(huì)使用時(shí)間戳選項(xiàng),它有兩個(gè)好處,一個(gè)是便于精確計(jì)算 RTT ,另一個(gè)是能防止序列號(hào)回繞(PAWS)。

試看下面的示例,假設(shè) TCP 的發(fā)送窗口是 1 GB,并且使用了時(shí)間戳選項(xiàng),發(fā)送方會(huì)為每個(gè) TCP 報(bào)文分配時(shí)間戳數(shù)值,我們假設(shè)每個(gè)報(bào)文時(shí)間加 1,然后使用這個(gè)連接傳輸一個(gè) 6GB 大小的數(shù)據(jù)流。

圖片

32 位的序列號(hào)在時(shí)刻 D 和 E 之間回繞。假設(shè)在時(shí)刻B有一個(gè)報(bào)文丟失并被重傳,又假設(shè)這個(gè)報(bào)文段在網(wǎng)絡(luò)上繞了遠(yuǎn)路并在時(shí)刻 F 重新出現(xiàn)。如果 TCP 無法識(shí)別這個(gè)繞回的報(bào)文,那么數(shù)據(jù)完整性就會(huì)遭到破壞。

使用時(shí)間戳選項(xiàng)能夠有效的防止上述問題,如果丟失的報(bào)文會(huì)在時(shí)刻 F 重新出現(xiàn),由于它的時(shí)間戳為 2,小于最近的有效時(shí)間戳(5 或 6),因此防回繞序列號(hào)算法(PAWS)會(huì)將其丟棄。

防回繞序列號(hào)算法要求連接雙方維護(hù)最近一次收到的數(shù)據(jù)包的時(shí)間戳(Recent TSval),每收到一個(gè)新數(shù)據(jù)包都會(huì)讀取數(shù)據(jù)包中的時(shí)間戳值跟 Recent TSval 值做比較,如果發(fā)現(xiàn)收到的數(shù)據(jù)包中時(shí)間戳不是遞增的,則表示該數(shù)據(jù)包是過期的,就會(huì)直接丟棄這個(gè)數(shù)據(jù)包。

客戶端和服務(wù)端的初始化序列號(hào)都是隨機(jī)生成,能很大程度上避免歷史報(bào)文被下一個(gè)相同四元組的連接接收,然后又引入時(shí)間戳的機(jī)制,從而完全避免了歷史報(bào)文被接收的問題。

你知道 TCP 的最大連接數(shù)嗎?

有一個(gè) IP 的服務(wù)器監(jiān)聽了一個(gè)端口,它的 TCP 的最大連接數(shù)是多少?

服務(wù)器通常固定在某個(gè)本地端口上監(jiān)聽,等待客戶端的連接請(qǐng)求。因此,客戶端 IP 和 端口是可變的,其理論值計(jì)算公式如下:

圖片

對(duì) IPv4,客戶端的 IP 數(shù)最多為 2的32次方,客戶端的端口數(shù)最多為 2的16次方,也就是服務(wù)端單機(jī)最大 TCP 連接數(shù),約為 2的48次方。

當(dāng)然,服務(wù)端最大并發(fā) TCP 連接數(shù)遠(yuǎn)不能達(dá)到理論上限:

  1. 首先主要是文件描述符限制,Socket 是文件,所以首先要通過 ulimit 配置文件描述符的數(shù)目;
  2. 另一個(gè)是內(nèi)存限制,每個(gè) TCP 連接都要占用一定內(nèi)存,操作系統(tǒng)的內(nèi)存是有限的。
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 操作系統(tǒng)
    +關(guān)注

    關(guān)注

    37

    文章

    7103

    瀏覽量

    125050
  • TCP
    TCP
    +關(guān)注

    關(guān)注

    8

    文章

    1398

    瀏覽量

    80468
  • ACK
    ACK
    +關(guān)注

    關(guān)注

    0

    文章

    28

    瀏覽量

    11346
  • 服務(wù)端
    +關(guān)注

    關(guān)注

    0

    文章

    68

    瀏覽量

    7199
收藏 人收藏

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    一文深入理解TCP/IP協(xié)議

    TCP/IP(Transmission Control Protocol/Internet Protocol,傳輸控制協(xié)議/網(wǎng)際協(xié)議)是指能夠在多個(gè)不同網(wǎng)絡(luò)間實(shí)現(xiàn)信息傳輸?shù)膮f(xié)議簇。
    發(fā)表于 07-15 10:44 ?1295次閱讀

    深入理解Android

    深入理解Android
    發(fā)表于 08-20 15:30

    深入理解和實(shí)現(xiàn)RTOS_連載

    和trcohili的帖子。深入理解和實(shí)現(xiàn)RTOS_連載1_RTOS的前生今世今天發(fā)布的是第一篇,"RTOS的前生今世"。通過軟件系統(tǒng)結(jié)構(gòu)的比對(duì)簡要的介紹rtos為何而生。如果讀者對(duì)RTOS
    發(fā)表于 05-29 11:20

    深入理解和實(shí)現(xiàn)RTOS_連載

    和trcohili的帖子。trochili rtos完全是作者興趣所在,且行且堅(jiān)持,比沒有duo。深入理解和實(shí)現(xiàn)RTOS_連載1_RTOS的前生今世今天發(fā)布的是第一篇,"RTOS的前生今世"
    發(fā)表于 05-30 01:02

    深入理解lte-a

    深入理解LTE-A
    發(fā)表于 02-26 10:21

    如何深入理解ES6之函數(shù)

    深入理解ES6之函數(shù)
    發(fā)表于 05-22 07:40

    TCP/UDP通信原理看完你就懂了

    深入理解TCP/UDP通信原理
    發(fā)表于 03-30 06:14

    深入理解STM32

    時(shí)鐘系統(tǒng)是處理器的核心,所以在學(xué)習(xí)STM32所有外設(shè)之前,認(rèn)真學(xué)習(xí)時(shí)鐘系統(tǒng)是必要的,有助于深入理解STM32。下面是從網(wǎng)上找的一個(gè)STM32時(shí)鐘框圖,比《STM32中文參考手冊》里面的是中途看起來清晰一些:重要的時(shí)鐘:PLLCLK,SYSCLK,HCKL,PCLK1,...
    發(fā)表于 08-12 07:46

    對(duì)棧的深入理解

    為什么要深入理解棧?做C語言開發(fā)如果棧設(shè)置不合理或者使用不對(duì),棧就會(huì)溢出,溢出就會(huì)遇到無法預(yù)測亂飛現(xiàn)象。所以對(duì)棧的深入理解是非常重要的。注:動(dòng)畫如果看不清楚可以電腦看更清晰啥是棧先來看一段動(dòng)畫:沒有
    發(fā)表于 02-15 07:01

    為什么要深入理解

    [導(dǎo)讀] 從這篇文章開始,將會(huì)不定期更新關(guān)于嵌入式C語言編程相關(guān)的個(gè)人認(rèn)為比較重要的知識(shí)點(diǎn),或者踩過的坑。為什么要深入理解棧?做C語言開發(fā)如果棧設(shè)置不合理或者使用不對(duì),棧就會(huì)溢出,溢出就會(huì)遇到無法
    發(fā)表于 02-15 06:09

    深入理解Android之資源文件

    深入理解Android之資源文件
    發(fā)表于 01-22 21:11 ?22次下載

    深入理解Android》文前

    深入理解Android》文前
    發(fā)表于 03-19 11:23 ?0次下載

    深入理解Android:卷I》

    深入理解Android:卷I》
    發(fā)表于 03-19 11:23 ?0次下載

    深入理解Android網(wǎng)絡(luò)編程

    深入理解Android網(wǎng)絡(luò)編程
    發(fā)表于 03-19 11:26 ?1次下載

    深入理解MOS管電子版資源下載

    深入理解MOS管電子版資源下載
    發(fā)表于 07-09 09:43 ?0次下載

    電子發(fā)燒友

    中國電子工程師最喜歡的網(wǎng)站

    • 2931785位工程師會(huì)員交流學(xué)習(xí)
    • 獲取您個(gè)性化的科技前沿技術(shù)信息
    • 參加活動(dòng)獲取豐厚的禮品