最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

任 TCP 虐我千百遍,我仍待 TCP 如初戀

2023-08-26 12:20 作者:程序員升職加薪  | 我要投稿

任 TCP 虐我千百遍,我仍待 TCP 如初戀。




巨巨巨巨長(zhǎng)的提綱,發(fā)車!發(fā)車!

PS:本次文章不涉及 TCP 流量控制、擁塞控制、可靠性傳輸?shù)确矫嬷R(shí),這些知識(shí)在這篇:你還在為 TCP 重傳、滑動(dòng)窗口、流量控制、擁塞控制發(fā)愁嗎?看完圖解就不愁了(opens new window)

#TCP 基本認(rèn)識(shí)

#TCP 頭格式有哪些?

我們先來看看 TCP 頭的格式,標(biāo)注顏色的表示與本文關(guān)聯(lián)比較大的字段,其他字段不做詳細(xì)闡述。

序列號(hào):在建立連接時(shí)由計(jì)算機(jī)生成的隨機(jī)數(shù)作為其初始值,通過 SYN 包傳給接收端主機(jī),每發(fā)送一次數(shù)據(jù),就「累加」一次該「數(shù)據(jù)字節(jié)數(shù)」的大小。用來解決網(wǎng)絡(luò)包亂序問題。

確認(rèn)應(yīng)答號(hào):指下一次「期望」收到的數(shù)據(jù)的序列號(hào),發(fā)送端收到這個(gè)確認(rèn)應(yīng)答以后可以認(rèn)為在這個(gè)序號(hào)以前的數(shù)據(jù)都已經(jīng)被正常接收。用來解決丟包的問題。

控制位:

  • ACK:該位為?1?時(shí),「確認(rèn)應(yīng)答」的字段變?yōu)橛行?,TCP 規(guī)定除了最初建立連接時(shí)的?SYN?包之外該位必須設(shè)置為?1?。

  • RST:該位為?1?時(shí),表示 TCP 連接中出現(xiàn)異常必須強(qiáng)制斷開連接。

  • SYN:該位為?1?時(shí),表示希望建立連接,并在其「序列號(hào)」的字段進(jìn)行序列號(hào)初始值的設(shè)定。

  • FIN:該位為?1?時(shí),表示今后不會(huì)再有數(shù)據(jù)發(fā)送,希望斷開連接。當(dāng)通信結(jié)束希望斷開連接時(shí),通信雙方的主機(jī)之間就可以相互交換?FIN?位為 1 的 TCP 段。

#為什么需要 TCP 協(xié)議? TCP 工作在哪一層?

IP?層是「不可靠」的,它不保證網(wǎng)絡(luò)包的交付、不保證網(wǎng)絡(luò)包的按序交付、也不保證網(wǎng)絡(luò)包中的數(shù)據(jù)的完整性。

如果需要保障網(wǎng)絡(luò)數(shù)據(jù)包的可靠性,那么就需要由上層(傳輸層)的?TCP?協(xié)議來負(fù)責(zé)。

因?yàn)?TCP 是一個(gè)工作在傳輸層可靠數(shù)據(jù)傳輸?shù)姆?wù),它能確保接收端接收的網(wǎng)絡(luò)包是無損壞、無間隔、非冗余和按序的。

#什么是 TCP ?

TCP 是面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議。

  • 面向連接:一定是「一對(duì)一」才能連接,不能像 UDP 協(xié)議可以一個(gè)主機(jī)同時(shí)向多個(gè)主機(jī)發(fā)送消息,也就是一對(duì)多是無法做到的;

  • 可靠的:無論的網(wǎng)絡(luò)鏈路中出現(xiàn)了怎樣的鏈路變化,TCP 都可以保證一個(gè)報(bào)文一定能夠到達(dá)接收端;

  • 字節(jié)流:用戶消息通過 TCP 協(xié)議傳輸時(shí),消息可能會(huì)被操作系統(tǒng)「分組」成多個(gè)的 TCP 報(bào)文,如果接收方的程序如果不知道「消息的邊界」,是無法讀出一個(gè)有效的用戶消息的。并且 TCP 報(bào)文是「有序的」,當(dāng)「前一個(gè)」TCP 報(bào)文沒有收到的時(shí)候,即使它先收到了后面的 TCP 報(bào)文,那么也不能扔給應(yīng)用層去處理,同時(shí)對(duì)「重復(fù)」的 TCP 報(bào)文會(huì)自動(dòng)丟棄。

#什么是 TCP 連接?

我們來看看 RFC 793 是如何定義「連接」的:

Connections: The reliability and flow control mechanisms described above require that TCPs initialize and maintain certain status information for each data stream. The combination of this information, including sockets, sequence numbers, and window sizes, is called a connection.

簡(jiǎn)單來說就是,用于保證可靠性和流量控制維護(hù)的某些狀態(tài)信息,這些信息的組合,包括 Socket、序列號(hào)和窗口大小稱為連接。

所以我們可以知道,建立一個(gè) TCP 連接是需要客戶端與服務(wù)端達(dá)成上述三個(gè)信息的共識(shí)。

  • Socket:由 IP 地址和端口號(hào)組成

  • 序列號(hào):用來解決亂序問題等

  • 窗口大小:用來做流量控制

#如何唯一確定一個(gè) TCP 連接呢?

TCP 四元組可以唯一的確定一個(gè)連接,四元組包括如下:

  • 源地址

  • 源端口

  • 目的地址

  • 目的端口

源地址和目的地址的字段(32 位)是在 IP 頭部中,作用是通過 IP 協(xié)議發(fā)送報(bào)文給對(duì)方主機(jī)。

源端口和目的端口的字段(16 位)是在 TCP 頭部中,作用是告訴 TCP 協(xié)議應(yīng)該把報(bào)文發(fā)給哪個(gè)進(jìn)程。

有一個(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á)到理論上限,會(huì)受以下因素影響:

  • 文件描述符限制,每個(gè) TCP 連接都是一個(gè)文件,如果文件描述符被占滿了,會(huì)發(fā)生 Too many open files。Linux 對(duì)可打開的文件描述符的數(shù)量分別作了三個(gè)方面的限制:

    • 系統(tǒng)級(jí):當(dāng)前系統(tǒng)可打開的最大數(shù)量,通過?cat /proc/sys/fs/file-max?查看;

    • 用戶級(jí):指定用戶可打開的最大數(shù)量,通過?cat /etc/security/limits.conf?查看;

    • 進(jìn)程級(jí):?jiǎn)蝹€(gè)進(jìn)程可打開的最大數(shù)量,通過?cat /proc/sys/fs/nr_open?查看;

  • 內(nèi)存限制,每個(gè) TCP 連接都要占用一定內(nèi)存,操作系統(tǒng)的內(nèi)存是有限的,如果內(nèi)存資源被占滿后,會(huì)發(fā)生 OOM。

#UDP 和 TCP 有什么區(qū)別呢?分別的應(yīng)用場(chǎng)景是?

UDP 不提供復(fù)雜的控制機(jī)制,利用 IP 提供面向「無連接」的通信服務(wù)。

UDP 協(xié)議真的非常簡(jiǎn),頭部只有?8?個(gè)字節(jié)(64 位),UDP 的頭部格式如下:

  • 目標(biāo)和源端口:主要是告訴 UDP 協(xié)議應(yīng)該把報(bào)文發(fā)給哪個(gè)進(jìn)程。

  • 包長(zhǎng)度:該字段保存了 UDP 首部的長(zhǎng)度跟數(shù)據(jù)的長(zhǎng)度之和。

  • 校驗(yàn)和:校驗(yàn)和是為了提供可靠的 UDP 首部和數(shù)據(jù)而設(shè)計(jì),防止收到在網(wǎng)絡(luò)傳輸中受損的 UDP 包。

TCP 和 UDP 區(qū)別:

1. 連接

  • TCP 是面向連接的傳輸層協(xié)議,傳輸數(shù)據(jù)前先要建立連接。

  • UDP 是不需要連接,即刻傳輸數(shù)據(jù)。

2. 服務(wù)對(duì)象

  • TCP 是一對(duì)一的兩點(diǎn)服務(wù),即一條連接只有兩個(gè)端點(diǎn)。

  • UDP 支持一對(duì)一、一對(duì)多、多對(duì)多的交互通信

3. 可靠性

  • TCP 是可靠交付數(shù)據(jù)的,數(shù)據(jù)可以無差錯(cuò)、不丟失、不重復(fù)、按序到達(dá)。

  • UDP 是盡最大努力交付,不保證可靠交付數(shù)據(jù)。但是我們可以基于 UDP 傳輸協(xié)議實(shí)現(xiàn)一個(gè)可靠的傳輸協(xié)議,比如 QUIC 協(xié)議,具體可以參見這篇文章:如何基于 UDP 協(xié)議實(shí)現(xiàn)可靠傳輸?(opens new window)

4. 擁塞控制、流量控制

  • TCP 有擁塞控制和流量控制機(jī)制,保證數(shù)據(jù)傳輸?shù)陌踩浴?/p>

  • UDP 則沒有,即使網(wǎng)絡(luò)非常擁堵了,也不會(huì)影響 UDP 的發(fā)送速率。

5. 首部開銷

  • TCP 首部長(zhǎng)度較長(zhǎng),會(huì)有一定的開銷,首部在沒有使用「選項(xiàng)」字段時(shí)是?20?個(gè)字節(jié),如果使用了「選項(xiàng)」字段則會(huì)變長(zhǎng)的。

  • UDP 首部只有 8 個(gè)字節(jié),并且是固定不變的,開銷較小。

6. 傳輸方式

  • TCP 是流式傳輸,沒有邊界,但保證順序和可靠。

  • UDP 是一個(gè)包一個(gè)包的發(fā)送,是有邊界的,但可能會(huì)丟包和亂序。

7. 分片不同

  • TCP 的數(shù)據(jù)大小如果大于 MSS 大小,則會(huì)在傳輸層進(jìn)行分片,目標(biāo)主機(jī)收到后,也同樣在傳輸層組裝 TCP 數(shù)據(jù)包,如果中途丟失了一個(gè)分片,只需要傳輸丟失的這個(gè)分片。

  • UDP 的數(shù)據(jù)大小如果大于 MTU 大小,則會(huì)在 IP 層進(jìn)行分片,目標(biāo)主機(jī)收到后,在 IP 層組裝完數(shù)據(jù),接著再傳給傳輸層。

TCP 和 UDP 應(yīng)用場(chǎng)景:

由于 TCP 是面向連接,能保證數(shù)據(jù)的可靠性交付,因此經(jīng)常用于:

  • FTP?文件傳輸;

  • HTTP / HTTPS;

由于 UDP 面向無連接,它可以隨時(shí)發(fā)送數(shù)據(jù),再加上 UDP 本身的處理既簡(jiǎn)單又高效,因此經(jīng)常用于:

  • 包總量較少的通信,如?DNS?、SNMP?等;

  • 視頻、音頻等多媒體通信;

  • 廣播通信;

為什么 UDP 頭部沒有「首部長(zhǎng)度」字段,而 TCP 頭部有「首部長(zhǎng)度」字段呢?

原因是 TCP 有可變長(zhǎng)的「選項(xiàng)」字段,而 UDP 頭部長(zhǎng)度則是不會(huì)變化的,無需多一個(gè)字段去記錄 UDP 的首部長(zhǎng)度。

為什么 UDP 頭部有「包長(zhǎng)度」字段,而 TCP 頭部則沒有「包長(zhǎng)度」字段呢?

先說說 TCP 是如何計(jì)算負(fù)載數(shù)據(jù)長(zhǎng)度:

其中 IP 總長(zhǎng)度 和 IP 首部長(zhǎng)度,在 IP 首部格式是已知的。TCP 首部長(zhǎng)度,則是在 TCP 首部格式已知的,所以就可以求得 TCP 數(shù)據(jù)的長(zhǎng)度。

大家這時(shí)就奇怪了問:“UDP 也是基于 IP 層的呀,那 UDP 的數(shù)據(jù)長(zhǎng)度也可以通過這個(gè)公式計(jì)算呀? 為何還要有「包長(zhǎng)度」呢?”

這么一問,確實(shí)感覺 UDP 的「包長(zhǎng)度」是冗余的。

我查閱了很多資料,我覺得有兩個(gè)比較靠譜的說法:

  • 第一種說法:因?yàn)闉榱司W(wǎng)絡(luò)設(shè)備硬件設(shè)計(jì)和處理方便,首部長(zhǎng)度需要是?4?字節(jié)的整數(shù)倍。如果去掉 UDP 的「包長(zhǎng)度」字段,那 UDP 首部長(zhǎng)度就不是?4?字節(jié)的整數(shù)倍了,所以我覺得這可能是為了補(bǔ)全 UDP 首部長(zhǎng)度是?4?字節(jié)的整數(shù)倍,才補(bǔ)充了「包長(zhǎng)度」字段。

  • 第二種說法:如今的 UDP 協(xié)議是基于 IP 協(xié)議發(fā)展的,而當(dāng)年可能并非如此,依賴的可能是別的不提供自身報(bào)文長(zhǎng)度或首部長(zhǎng)度的網(wǎng)絡(luò)層協(xié)議,因此 UDP 報(bào)文首部需要有長(zhǎng)度字段以供計(jì)算。

#TCP 和 UDP 可以使用同一個(gè)端口嗎?

答案:可以的。

在數(shù)據(jù)鏈路層中,通過 MAC 地址來尋找局域網(wǎng)中的主機(jī)。在網(wǎng)際層中,通過 IP 地址來尋找網(wǎng)絡(luò)中互連的主機(jī)或路由器。在傳輸層中,需要通過端口進(jìn)行尋址,來識(shí)別同一計(jì)算機(jī)中同時(shí)通信的不同應(yīng)用程序。

所以,傳輸層的「端口號(hào)」的作用,是為了區(qū)分同一個(gè)主機(jī)上不同應(yīng)用程序的數(shù)據(jù)包。

傳輸層有兩個(gè)傳輸協(xié)議分別是 TCP 和 UDP,在內(nèi)核中是兩個(gè)完全獨(dú)立的軟件模塊。

當(dāng)主機(jī)收到數(shù)據(jù)包后,可以在 IP 包頭的「協(xié)議號(hào)」字段知道該數(shù)據(jù)包是 TCP/UDP,所以可以根據(jù)這個(gè)信息確定送給哪個(gè)模塊(TCP/UDP)處理,送給 TCP/UDP 模塊的報(bào)文根據(jù)「端口號(hào)」確定送給哪個(gè)應(yīng)用程序處理。

因此,TCP/UDP 各自的端口號(hào)也相互獨(dú)立,如 TCP 有一個(gè) 80 號(hào)端口,UDP 也可以有一個(gè) 80 號(hào)端口,二者并不沖突。

關(guān)于端口的知識(shí)點(diǎn),還是挺多可以講的,比如還可以牽扯到這幾個(gè)問題:

  • 多個(gè) TCP 服務(wù)進(jìn)程可以同時(shí)綁定同一個(gè)端口嗎?

  • 重啟 TCP 服務(wù)進(jìn)程時(shí),為什么會(huì)出現(xiàn)“Address in use”的報(bào)錯(cuò)信息?又該怎么避免?

  • 客戶端的端口可以重復(fù)使用嗎?

  • 客戶端 TCP 連接 TIME_WAIT 狀態(tài)過多,會(huì)導(dǎo)致端口資源耗盡而無法建立新的連接嗎?

上面這些問題,可以看這篇文章:TCP 和 UDP 可以使用同一個(gè)端口嗎?(opens new window)

#TCP 連接建立

#TCP 三次握手過程是怎樣的?

TCP 是面向連接的協(xié)議,所以使用 TCP 前必須先建立連接,而建立連接是通過三次握手來進(jìn)行的。三次握手的過程如下圖:

  • 一開始,客戶端和服務(wù)端都處于?CLOSE?狀態(tài)。先是服務(wù)端主動(dòng)監(jiān)聽某個(gè)端口,處于?LISTEN?狀態(tài)

  • 客戶端會(huì)隨機(jī)初始化序號(hào)(client_isn),將此序號(hào)置于 TCP 首部的「序號(hào)」字段中,同時(shí)把?SYN?標(biāo)志位置為?1,表示?SYN?報(bào)文。接著把第一個(gè) SYN 報(bào)文發(fā)送給服務(wù)端,表示向服務(wù)端發(fā)起連接,該報(bào)文不包含應(yīng)用層數(shù)據(jù),之后客戶端處于?SYN-SENT?狀態(tài)。

  • 服務(wù)端收到客戶端的?SYN?報(bào)文后,首先服務(wù)端也隨機(jī)初始化自己的序號(hào)(server_isn),將此序號(hào)填入 TCP 首部的「序號(hào)」字段中,其次把 TCP 首部的「確認(rèn)應(yīng)答號(hào)」字段填入?client_isn + 1, 接著把?SYN?和?ACK?標(biāo)志位置為?1。最后把該報(bào)文發(fā)給客戶端,該報(bào)文也不包含應(yīng)用層數(shù)據(jù),之后服務(wù)端處于?SYN-RCVD?狀態(tài)。

  • 客戶端收到服務(wù)端報(bào)文后,還要向服務(wù)端回應(yīng)最后一個(gè)應(yīng)答報(bào)文,首先該應(yīng)答報(bào)文 TCP 首部?ACK?標(biāo)志位置為?1?,其次「確認(rèn)應(yīng)答號(hào)」字段填入?server_isn + 1?,最后把報(bào)文發(fā)送給服務(wù)端,這次報(bào)文可以攜帶客戶到服務(wù)端的數(shù)據(jù),之后客戶端處于?ESTABLISHED?狀態(tài)。

  • 服務(wù)端收到客戶端的應(yīng)答報(bào)文后,也進(jìn)入?ESTABLISHED?狀態(tài)。

從上面的過程可以發(fā)現(xiàn)第三次握手是可以攜帶數(shù)據(jù)的,前兩次握手是不可以攜帶數(shù)據(jù)的,這也是面試常問的題。

一旦完成三次握手,雙方都處于?ESTABLISHED?狀態(tài),此時(shí)連接就已建立完成,客戶端和服務(wù)端就可以相互發(fā)送數(shù)據(jù)了。

#如何在 Linux 系統(tǒng)中查看 TCP 狀態(tài)?

TCP 的連接狀態(tài)查看,在 Linux 可以通過?netstat -napt?命令查看。

#為什么是三次握手?不是兩次、四次?

相信大家比較?;卮鸬氖牵骸耙?yàn)槿挝帐植拍鼙WC雙方具有接收和發(fā)送的能力?!?/p>

這回答是沒問題,但這回答是片面的,并沒有說出主要的原因。

在前面我們知道了什么是?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.

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

我們考慮一個(gè)場(chǎng)景,客戶端先發(fā)送了 SYN(seq = 90)報(bào)文,然后客戶端宕機(jī)了,而且這個(gè) SYN 報(bào)文還被網(wǎng)絡(luò)阻塞了,服務(wù)端并沒有收到,接著客戶端重啟后,又重新向服務(wù)端建立連接,發(fā)送了 SYN(seq = 100)報(bào)文(注意!不是重傳 SYN,重傳的 SYN 的序列號(hào)是一樣的)。

看看三次握手是如何阻止歷史連接的:

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

  • 一個(gè)「舊 SYN 報(bào)文」比「最新的 SYN」 報(bào)文早到達(dá)了服務(wù)端,那么此時(shí)服務(wù)端就會(huì)回一個(gè)?SYN + ACK?報(bào)文給客戶端,此報(bào)文中的確認(rèn)號(hào)是 91(90+1)。

  • 客戶端收到后,發(fā)現(xiàn)自己期望收到的確認(rèn)號(hào)應(yīng)該是 100 + 1,而不是 90 + 1,于是就會(huì)回 RST 報(bào)文。

  • 服務(wù)端收到 RST 報(bào)文后,就會(huì)釋放連接。

  • 后續(xù)最新的 SYN 抵達(dá)了服務(wù)端后,客戶端與服務(wù)端就可以正常的完成三次握手了。

上述中的「舊 SYN 報(bào)文」稱為歷史連接,TCP 使用三次握手建立連接的最主要原因就是防止「歷史連接」初始化了連接。

TIP

有很多人問,如果服務(wù)端在收到 RST 報(bào)文之前,先收到了「新 SYN 報(bào)文」,也就是服務(wù)端收到客戶端報(bào)文的順序是:「舊 SYN 報(bào)文」->「新 SYN 報(bào)文」,此時(shí)會(huì)發(fā)生什么?

當(dāng)服務(wù)端第一次收到 SYN 報(bào)文,也就是收到 「舊 SYN 報(bào)文」時(shí),就會(huì)回復(fù)?SYN + ACK?報(bào)文給客戶端,此報(bào)文中的確認(rèn)號(hào)是 91(90+1)。

然后這時(shí)再收到「新 SYN 報(bào)文」時(shí),就會(huì)回?Challenge Ack?(opens new window)報(bào)文給客戶端,這個(gè) ack 報(bào)文并不是確認(rèn)收到「新 SYN 報(bào)文」的,而是上一次的 ack 確認(rèn)號(hào),也就是91(90+1)。所以客戶端收到此 ACK 報(bào)文時(shí),發(fā)現(xiàn)自己期望收到的確認(rèn)號(hào)應(yīng)該是 101,而不是 91,于是就會(huì)回 RST 報(bào)文。

如果是兩次握手連接,就無法阻止歷史連接,那為什么 TCP 兩次握手為什么無法阻止歷史連接呢?

我先直接說結(jié)論,主要是因?yàn)?strong>在兩次握手的情況下,服務(wù)端沒有中間狀態(tài)給客戶端來阻止歷史連接,導(dǎo)致服務(wù)端可能建立一個(gè)歷史連接,造成資源浪費(fèi)。

你想想,在兩次握手的情況下,服務(wù)端在收到 SYN 報(bào)文后,就進(jìn)入 ESTABLISHED 狀態(tài),意味著這時(shí)可以給對(duì)方發(fā)送數(shù)據(jù),但是客戶端此時(shí)還沒有進(jìn)入 ESTABLISHED 狀態(tài),假設(shè)這次是歷史連接,客戶端判斷到此次連接為歷史連接,那么就會(huì)回 RST 報(bào)文來斷開連接,而服務(wù)端在第一次握手的時(shí)候就進(jìn)入 ESTABLISHED 狀態(tài),所以它可以發(fā)送數(shù)據(jù)的,但是它并不知道這個(gè)是歷史連接,它只有在收到 RST 報(bào)文后,才會(huì)斷開連接。

可以看到,如果采用兩次握手建立 TCP 連接的場(chǎng)景下,服務(wù)端在向客戶端發(fā)送數(shù)據(jù)前,并沒有阻止掉歷史連接,導(dǎo)致服務(wù)端建立了一個(gè)歷史連接,又白白發(fā)送了數(shù)據(jù),妥妥地浪費(fèi)了服務(wù)端的資源。

因此,要解決這種現(xiàn)象,最好就是在服務(wù)端發(fā)送數(shù)據(jù)前,也就是建立連接之前,要阻止掉歷史連接,這樣就不會(huì)造成資源浪費(fèi),而要實(shí)現(xiàn)這個(gè)功能,就需要三次握手。

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

TIP

有人問:客戶端發(fā)送三次握手(ack 報(bào)文)后就可以發(fā)送數(shù)據(jù)了,而被動(dòng)方此時(shí)還是 syn_received 狀態(tài),如果 ack 丟了,那客戶端發(fā)的數(shù)據(jù)是不是也白白浪費(fèi)了?

不是的,即使服務(wù)端還是在 syn_received 狀態(tài),收到了客戶端發(fā)送的數(shù)據(jù),還是可以建立連接的,并且還可以正常收到這個(gè)數(shù)據(jù)包。這是因?yàn)閿?shù)據(jù)報(bào)文中是有 ack 標(biāo)識(shí)位,也有確認(rèn)號(hào),這個(gè)確認(rèn)號(hào)就是確認(rèn)收到了第二次握手。如下圖:

所以,服務(wù)端收到這個(gè)數(shù)據(jù)報(bào)文,是可以正常建立連接的,然后就可以正常接收這個(gè)數(shù)據(jù)包了。

原因二:同步雙方初始序列號(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ì)方收到的(通過 ACK 報(bào)文中的序列號(hào)知道);

可見,序列號(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)客戶端發(fā)生的?SYN?報(bào)文在網(wǎng)絡(luò)中阻塞,客戶端沒有接收到?ACK?報(bào)文,就會(huì)重新發(fā)送?SYN?,由于沒有第三次握手,服務(wù)端不清楚客戶端是否收到了自己回復(fù)的?ACK?報(bào)文,所以服務(wù)端每收到一個(gè)?SYN?就只能先主動(dòng)建立一個(gè)連接,這會(huì)造成什么情況呢?

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

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

TIP

很多人問,兩次握手不是也可以根據(jù)上下文信息丟棄 syn 歷史報(bào)文嗎?

我這里兩次握手是假設(shè)「由于沒有第三次握手,服務(wù)端不清楚客戶端是否收到了自己發(fā)送的建立連接的?ACK?確認(rèn)報(bào)文,所以每收到一個(gè)?SYN?就只能先主動(dòng)建立一個(gè)連接」這個(gè)場(chǎng)景。

當(dāng)然你要實(shí)現(xiàn)成類似三次握手那樣,根據(jù)上下文丟棄 syn 歷史報(bào)文也是可以的,兩次握手沒有具體的實(shí)現(xiàn),怎么假設(shè)都行。

小結(jié)

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

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

  • 「兩次握手」:無法防止歷史連接的建立,會(huì)造成雙方資源的浪費(fèi),也無法可靠的同步雙方序列號(hào);

  • 「四次握手」:三次握手就已經(jīng)理論上最少可靠連接建立,所以不需要使用更多的通信次數(shù)。

#為什么每次建立 TCP 連接時(shí),初始化的序列號(hào)都要求不一樣呢?

主要原因有兩個(gè)方面:

  • 為了防止歷史報(bào)文被下一個(gè)相同四元組的連接接收(主要方面);

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

接下來,詳細(xì)說說第一點(diǎn)。

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

過程如下:

  • 客戶端和服務(wù)端建立一個(gè) TCP 連接,在客戶端發(fā)送數(shù)據(jù)包被網(wǎng)絡(luò)阻塞了,然后超時(shí)重傳了這個(gè)數(shù)據(jù)包,而此時(shí)服務(wù)端設(shè)備斷電重啟了,之前與客戶端建立的連接就消失了,于是在收到客戶端的數(shù)據(jù)包的時(shí)候就會(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)都「不一樣」,就有大概率因?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è)相同四元組的連接接收,注意是很大程度上,并不是完全避免了(因?yàn)樾蛄刑?hào)會(huì)有回繞的問題,所以需要用時(shí)間戳的機(jī)制來判斷歷史報(bào)文,詳細(xì)看篇:TCP 是如何避免歷史報(bào)文的??(opens new window))。

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

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

RFC793 提到初始化序列號(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è)比較好的選擇。

可以看到,隨機(jī)數(shù)是會(huì)基于時(shí)鐘計(jì)時(shí)器遞增的,基本不可能會(huì)隨機(jī)成一樣的初始化序列號(hào)。

#既然 IP 層會(huì)分片,為什么 TCP 層還需要 MSS 呢?

我們先來認(rèn)識(shí)下 MTU 和 MSS

  • MTU:一個(gè)網(wǎng)絡(luò)包的最大長(zhǎng)度,以太網(wǎng)中一般為?1500?字節(jié);

  • MSS:除去 IP 和 TCP 頭部之后,一個(gè)網(wǎng)絡(luò)包所能容納的 TCP 數(shù)據(jù)的最大長(zhǎng)度;

如果在 TCP 的整個(gè)報(bào)文(頭部 + 數(shù)據(jù))交給 IP 層進(jìn)行分片,會(huì)有什么異常呢?

當(dāng) IP 層有一個(gè)超過?MTU?大小的數(shù)據(jù)(TCP 頭部 + TCP 數(shù)據(jù))要發(fā)送,那么 IP 層就要進(jìn)行分片,把數(shù)據(jù)分片成若干片,保證每一個(gè)分片都小于 MTU。把一份 IP 數(shù)據(jù)報(bào)進(jìn)行分片以后,由目標(biāo)主機(jī)的 IP 層來進(jìn)行重新組裝后,再交給上一層 TCP 傳輸層。

這看起來井然有序,但這存在隱患的,那么當(dāng)如果一個(gè) IP 分片丟失,整個(gè) IP 報(bào)文的所有分片都得重傳。

因?yàn)?IP 層本身沒有超時(shí)重傳機(jī)制,它由傳輸層的 TCP 來負(fù)責(zé)超時(shí)和重傳。

當(dāng)某一個(gè) IP 分片丟失后,接收方的 IP 層就無法組裝成一個(gè)完整的 TCP 報(bào)文(頭部 + 數(shù)據(jù)),也就無法將數(shù)據(jù)報(bào)文送到 TCP 層,所以接收方不會(huì)響應(yīng) ACK 給發(fā)送方,因?yàn)榘l(fā)送方遲遲收不到 ACK 確認(rèn)報(bào)文,所以會(huì)觸發(fā)超時(shí)重傳,就會(huì)重發(fā)「整個(gè) TCP 報(bào)文(頭部 + 數(shù)據(jù))」。

因此,可以得知由 IP 層進(jìn)行分片傳輸,是非常沒有效率的。

所以,為了達(dá)到最佳的傳輸效能 TCP 協(xié)議在建立連接的時(shí)候通常要協(xié)商雙方的 MSS 值,當(dāng) TCP 層發(fā)現(xiàn)數(shù)據(jù)超過 MSS 時(shí),則就先會(huì)進(jìn)行分片,當(dāng)然由它形成的 IP 包的長(zhǎng)度也就不會(huì)大于 MTU ,自然也就不用 IP 分片了。

經(jīng)過 TCP 層分片后,如果一個(gè) TCP 分片丟失后,進(jìn)行重發(fā)時(shí)也是以 MSS 為單位,而不用重傳所有的分片,大大增加了重傳的效率。

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

當(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ī)制,重傳 SYN 報(bào)文,而且重傳的 SYN 報(bào)文的序列號(hào)都是一樣的。

不同版本的操作系統(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。

# cat /proc/sys/net/ipv4/tcp_syn_retries5

通常,第一次超時(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 分鐘左右。

舉個(gè)例子,假設(shè) tcp_syn_retries 參數(shù)值為 3,那么當(dāng)客戶端的 SYN 報(bào)文一直在網(wǎng)絡(luò)中丟失時(shí),會(huì)發(fā)生下圖的過程:

具體過程:

  • 當(dāng)客戶端超時(shí)重傳 3 次 SYN 報(bào)文后,由于 tcp_syn_retries 為 3,已達(dá)到最大重傳次數(shù),于是再等待一段時(shí)間(時(shí)間為上一次超時(shí)時(shí)間的 2 倍),如果還是沒能收到服務(wù)端的第二次握手(SYN-ACK 報(bào)文),那么客戶端就會(huì)斷開連接。

#第二次握手丟失了,會(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。

# cat /proc/sys/net/ipv4/tcp_synack_retries5

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

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

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

舉個(gè)例子,假設(shè) tcp_syn_retries 參數(shù)值為 1,tcp_synack_retries 參數(shù)值為 2,那么當(dāng)?shù)诙挝帐忠恢眮G失時(shí),發(fā)生的過程如下圖:

具體過程:

  • 當(dāng)客戶端超時(shí)重傳 1 次 SYN 報(bào)文后,由于 tcp_syn_retries 為 1,已達(dá)到最大重傳次數(shù),于是再等待一段時(shí)間(時(shí)間為上一次超時(shí)時(shí)間的 2 倍),如果還是沒能收到服務(wù)端的第二次握手(SYN-ACK 報(bào)文),那么客戶端就會(huì)斷開連接。

  • 當(dāng)服務(wù)端超時(shí)重傳 2 次 SYN-ACK 報(bào)文后,由于 tcp_synack_retries 為 2,已達(dá)到最大重傳次數(shù),于是再等待一段時(shí)間(時(shí)間為上一次超時(shí)時(shí)間的 2 倍),如果還是沒能收到客戶端的第三次握手(ACK 報(bào)文),那么服務(wù)端就會(huì)斷開連接。

#第三次握手丟失了,會(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)文。

舉個(gè)例子,假設(shè) tcp_synack_retries 參數(shù)值為 2,那么當(dāng)?shù)谌挝帐忠恢眮G失時(shí),發(fā)生的過程如下圖:

具體過程:

  • 當(dāng)服務(wù)端超時(shí)重傳 2 次 SYN-ACK 報(bào)文后,由于 tcp_synack_retries 為 2,已達(dá)到最大重傳次數(shù),于是再等待一段時(shí)間(時(shí)間為上一次超時(shí)時(shí)間的 2 倍),如果還是沒能收到客戶端的第三次握手(ACK 報(bào)文),那么服務(wù)端就會(huì)斷開連接。

#什么是 SYN 攻擊?如何避免 SYN 攻擊?

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

先跟大家說一下,什么是 TCP 半連接和全連接隊(duì)列。

在 TCP 三次握手的時(shí)候,Linux 內(nèi)核會(huì)維護(hù)兩個(gè)隊(duì)列,分別是:

  • 半連接隊(duì)列,也稱 SYN 隊(duì)列;

  • 全連接隊(duì)列,也稱 accept 隊(duì)列;

我們先來看下 Linux 內(nèi)核的?SYN?隊(duì)列(半連接隊(duì)列)與?Accpet?隊(duì)列(全連接隊(duì)列)是如何工作的?

正常流程:

  • 當(dāng)服務(wù)端接收到客戶端的 SYN 報(bào)文時(shí),會(huì)創(chuàng)建一個(gè)半連接的對(duì)象,然后將其加入到內(nèi)核的「 SYN 隊(duì)列」;

  • 接著發(fā)送 SYN + ACK 給客戶端,等待客戶端回應(yīng) ACK 報(bào)文;

  • 服務(wù)端接收到 ACK 報(bào)文后,從「 SYN 隊(duì)列」取出一個(gè)半連接對(duì)象,然后創(chuàng)建一個(gè)新的連接對(duì)象放入到「 Accept 隊(duì)列」;

  • 應(yīng)用通過調(diào)用?accpet()?socket 接口,從「 Accept 隊(duì)列」取出連接對(duì)象。

不管是半連接隊(duì)列還是全連接隊(duì)列,都有最大長(zhǎng)度限制,超過限制時(shí),默認(rèn)情況都會(huì)丟棄報(bào)文。

SYN 攻擊方式最直接的表現(xiàn)就會(huì)把 TCP 半連接隊(duì)列打滿,這樣當(dāng) TCP 半連接隊(duì)列滿了,后續(xù)再在收到 SYN 報(bào)文就會(huì)丟棄,導(dǎo)致客戶端無法和服務(wù)端建立連接。

避免 SYN 攻擊方式,可以有以下四種方法:

  • 調(diào)大 netdev_max_backlog;

  • 增大 TCP 半連接隊(duì)列;

  • 開啟 tcp_syncookies;

  • 減少 SYN+ACK 重傳次數(shù)

方式一:調(diào)大 netdev_max_backlog

當(dāng)網(wǎng)卡接收數(shù)據(jù)包的速度大于內(nèi)核處理的速度時(shí),會(huì)有一個(gè)隊(duì)列保存這些數(shù)據(jù)包。控制該隊(duì)列的最大值如下參數(shù),默認(rèn)值是 1000,我們要適當(dāng)調(diào)大該參數(shù)的值,比如設(shè)置為 10000:

net.core.netdev_max_backlog = 10000

方式二:增大 TCP 半連接隊(duì)列

增大 TCP 半連接隊(duì)列,要同時(shí)增大下面這三個(gè)參數(shù):

  • 增大 net.ipv4.tcp_max_syn_backlog

  • 增大 listen() 函數(shù)中的 backlog

  • 增大 net.core.somaxconn

具體為什么是三個(gè)參數(shù)決定 TCP 半連接隊(duì)列的大小,可以看這篇:可以看這篇:TCP 半連接隊(duì)列和全連接隊(duì)列滿了會(huì)發(fā)生什么?又該如何應(yīng)對(duì)?(opens new window)

方式三:開啟 net.ipv4.tcp_syncookies

開啟 syncookies 功能就可以在不使用 SYN 半連接隊(duì)列的情況下成功建立連接,相當(dāng)于繞過了 SYN 半連接來建立連接。

具體過程:

  • 當(dāng) 「 SYN 隊(duì)列」?jié)M之后,后續(xù)服務(wù)端收到 SYN 包,不會(huì)丟棄,而是根據(jù)算法,計(jì)算出一個(gè)?cookie?值;

  • 將 cookie 值放到第二次握手報(bào)文的「序列號(hào)」里,然后服務(wù)端回第二次握手給客戶端;

  • 服務(wù)端接收到客戶端的應(yīng)答報(bào)文時(shí),服務(wù)端會(huì)檢查這個(gè) ACK 包的合法性。如果合法,將該連接對(duì)象放入到「 Accept 隊(duì)列」。

  • 最后應(yīng)用程序通過調(diào)用?accpet()?接口,從「 Accept 隊(duì)列」取出的連接。

可以看到,當(dāng)開啟了 tcp_syncookies 了,即使受到 SYN 攻擊而導(dǎo)致 SYN 隊(duì)列滿時(shí),也能保證正常的連接成功建立。

net.ipv4.tcp_syncookies 參數(shù)主要有以下三個(gè)值:

  • 0 值,表示關(guān)閉該功能;

  • 1 值,表示僅當(dāng) SYN 半連接隊(duì)列放不下時(shí),再啟用它;

  • 2 值,表示無條件開啟功能;

那么在應(yīng)對(duì) SYN 攻擊時(shí),只需要設(shè)置為 1 即可。

$ echo 1 > /proc/sys/net/ipv4/tcp_syncookies

方式四:減少 SYN+ACK 重傳次數(shù)

當(dāng)服務(wù)端受到 SYN 攻擊時(shí),就會(huì)有大量處于 SYN_REVC 狀態(tài)的 TCP 連接,處于這個(gè)狀態(tài)的 TCP 會(huì)重傳 SYN+ACK ,當(dāng)重傳超過次數(shù)達(dá)到上限后,就會(huì)斷開連接。

那么針對(duì) SYN 攻擊的場(chǎng)景,我們可以減少 SYN-ACK 的重傳次數(shù),以加快處于 SYN_REVC 狀態(tài)的 TCP 連接斷開。

SYN-ACK 報(bào)文的最大重傳次數(shù)由?tcp_synack_retries內(nèi)核參數(shù)決定(默認(rèn)值是 5 次),比如將 tcp_synack_retries 減少到 2 次:

$ echo 2 > /proc/sys/net/ipv4/tcp_synack_retries

#TCP 連接斷開

#TCP 四次揮手過程是怎樣的?

天下沒有不散的宴席,對(duì)于 TCP 連接也是這樣, TCP 斷開連接是通過四次揮手方式。

雙方都可以主動(dòng)斷開連接,斷開連接后主機(jī)中的「資源」將被釋放,四次揮手的過程如下圖:

  • 客戶端打算關(guān)閉連接,此時(shí)會(huì)發(fā)送一個(gè) TCP 首部?FIN?標(biāo)志位被置為?1?的報(bào)文,也即?FIN?報(bào)文,之后客戶端進(jìn)入?FIN_WAIT_1?狀態(tài)。

  • 服務(wù)端收到該報(bào)文后,就向客戶端發(fā)送?ACK?應(yīng)答報(bào)文,接著服務(wù)端進(jìn)入?CLOSE_WAIT?狀態(tài)。

  • 客戶端收到服務(wù)端的?ACK?應(yīng)答報(bào)文后,之后進(jìn)入?FIN_WAIT_2?狀態(tài)。

  • 等待服務(wù)端處理完數(shù)據(jù)后,也向客戶端發(fā)送?FIN?報(bào)文,之后服務(wù)端進(jìn)入?LAST_ACK?狀態(tài)。

  • 客戶端收到服務(wù)端的?FIN?報(bào)文后,回一個(gè)?ACK?應(yīng)答報(bào)文,之后進(jìn)入?TIME_WAIT?狀態(tài)

  • 服務(wù)端收到了?ACK?應(yīng)答報(bào)文后,就進(jìn)入了?CLOSE?狀態(tài),至此服務(wù)端已經(jīng)完成連接的關(guān)閉。

  • 客戶端在經(jīng)過?2MSL?一段時(shí)間后,自動(dòng)進(jìn)入?CLOSE?狀態(tài),至此客戶端也完成連接的關(guān)閉。

你可以看到,每個(gè)方向都需要一個(gè) FIN 和一個(gè) ACK,因此通常被稱為四次揮手。

這里一點(diǎn)需要注意是:主動(dòng)關(guān)閉連接的,才有 TIME_WAIT 狀態(tài)。

#為什么揮手需要四次?

再來回顧下四次揮手雙方發(fā)?FIN?包的過程,就能理解為什么需要四次了。

  • 關(guān)閉連接時(shí),客戶端向服務(wù)端發(fā)送?FIN?時(shí),僅僅表示客戶端不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù)。

  • 服務(wù)端收到客戶端的?FIN?報(bào)文時(shí),先回一個(gè)?ACK?應(yīng)答報(bào)文,而服務(wù)端可能還有數(shù)據(jù)需要處理和發(fā)送,等服務(wù)端不再發(fā)送數(shù)據(jù)時(shí),才發(fā)送?FIN?報(bào)文給客戶端來表示同意現(xiàn)在關(guān)閉連接。

從上面過程可知,服務(wù)端通常需要等待完成數(shù)據(jù)的發(fā)送和處理,所以服務(wù)端的?ACK?和?FIN?一般都會(huì)分開發(fā)送,因此是需要四次揮手。

但是在特定情況下,四次揮手是可以變成三次揮手的,具體情況可以看這篇:TCP 四次揮手,可以變成三次嗎?(opens new window)

#第一次揮手丟失了,會(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)文,則會(huì)在等待一段時(shí)間(時(shí)間為上一次超時(shí)時(shí)間的 2 倍),如果還是沒能收到第二次揮手,那么直接進(jìn)入到?close?狀態(tài)。

舉個(gè)例子,假設(shè) tcp_orphan_retries 參數(shù)值為 3,當(dāng)?shù)谝淮螕]手一直丟失時(shí),發(fā)生的過程如下圖:

具體過程:

  • 當(dāng)客戶端超時(shí)重傳 3 次 FIN 報(bào)文后,由于 tcp_orphan_retries 為 3,已達(dá)到最大重傳次數(shù),于是再等待一段時(shí)間(時(shí)間為上一次超時(shí)時(shí)間的 2 倍),如果還是沒能收到服務(wù)端的第二次揮手(ACK報(bào)文),那么客戶端就會(huì)斷開連接。

#第二次揮手丟失了,會(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ù)。

舉個(gè)例子,假設(shè) tcp_orphan_retries 參數(shù)值為 2,當(dāng)?shù)诙螕]手一直丟失時(shí),發(fā)生的過程如下圖:

具體過程:

  • 當(dāng)客戶端超時(shí)重傳 2 次 FIN 報(bào)文后,由于 tcp_orphan_retries 為 2,已達(dá)到最大重傳次數(shù),于是再等待一段時(shí)間(時(shí)間為上一次超時(shí)時(shí)間的 2 倍),如果還是沒能收到服務(wù)端的第二次揮手(ACK 報(bào)文),那么客戶端就會(huì)斷開連接。

這里提一下,當(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í)長(zhǎng),默認(rèn)值是 60 秒。

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

但是注意,如果主動(dòng)關(guān)閉方使用 shutdown 函數(shù)關(guān)閉連接,指定了只關(guān)閉發(fā)送方向,而接收方向并沒有關(guān)閉,那么意味著主動(dòng)關(guān)閉方還是可以接收數(shù)據(jù)的。

此時(shí),如果主動(dòng)關(guān)閉方一直沒收到第三次揮手,那么主動(dòng)關(guān)閉方的連接將會(huì)一直處于?FIN_WAIT2?狀態(tài)(tcp_fin_timeout?無法控制 shutdown 關(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ù)控制方式是一樣的。

舉個(gè)例子,假設(shè)?tcp_orphan_retries = 3,當(dāng)?shù)谌螕]手一直丟失時(shí),發(fā)生的過程如下圖:

具體過程:

  • 當(dāng)服務(wù)端重傳第三次揮手報(bào)文的次數(shù)達(dá)到了 3 次后,由于 tcp_orphan_retries 為 3,達(dá)到了重傳最大次數(shù),于是再等待一段時(shí)間(時(shí)間為上一次超時(shí)時(shí)間的 2 倍),如果還是沒能收到客戶端的第四次揮手(ACK報(bào)文),那么服務(wù)端就會(huì)斷開連接。

  • 客戶端因?yàn)槭峭ㄟ^ close 函數(shù)關(guān)閉連接的,處于 FIN_WAIT_2 狀態(tài)是有時(shí)長(zhǎng)限制的,如果 tcp_fin_timeout 時(shí)間內(nèi)還是沒能收到服務(wù)端的第三次揮手(FIN 報(bào)文),那么客戶端就會(huì)斷開連接。

#第四次揮手丟失了,會(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ù) 2MSL 后才會(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ù)控制。

舉個(gè)例子,假設(shè) tcp_orphan_retries 為 2,當(dāng)?shù)谒拇螕]手一直丟失時(shí),發(fā)生的過程如下:

具體過程:

  • 當(dāng)服務(wù)端重傳第三次揮手報(bào)文達(dá)到 2 時(shí),由于 tcp_orphan_retries 為 2, 達(dá)到了最大重傳次數(shù),于是再等待一段時(shí)間(時(shí)間為上一次超時(shí)時(shí)間的 2 倍),如果還是沒能收到客戶端的第四次揮手(ACK 報(bào)文),那么服務(wù)端就會(huì)斷開連接。

  • 客戶端在收到第三次揮手后,就會(huì)進(jìn)入 TIME_WAIT 狀態(tài),開啟時(shí)長(zhǎng)為 2MSL 的定時(shí)器,如果途中再次收到第三次揮手(FIN 報(bào)文)后,就會(huì)重置定時(shí)器,當(dāng)?shù)却?2MSL 時(shí)長(zhǎng)后,客戶端就會(huì)斷開連接。

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

MSL?是 Maximum Segment Lifetime,報(bào)文最大生存時(shí)間,它是任何報(bào)文在網(wǎng)絡(luò)上存在的最長(zhǎng)時(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)文已被自然消亡。

TTL 的值一般是 64,Linux 將 MSL 設(shè)置為 30 秒,意味著 Linux 認(rèn)為數(shù)據(jù)報(bào)文經(jīng)過 64 個(gè)路由器的時(shí)間不會(huì)超過 30 秒,如果超過了,就認(rèn)為報(bào)文已經(jīng)消失在網(wǎng)絡(luò)中了

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í)長(zhǎng)?這其實(shí)是相當(dāng)于至少允許報(bào)文丟失一次。比如,若 ACK 在一個(gè) MSL 內(nèi)丟失,這樣被動(dòng)方重發(fā)的 FIN 會(huì)在第 2 個(gè) MSL 內(nèi)到達(dá),TIME_WAIT 狀態(tài)的連接可以應(yīng)對(duì)。

為什么不是 4 或者 8 MSL 的時(shí)長(zhǎng)呢?你可以想象一個(gè)丟包率達(dá)到百分之一的糟糕網(wǎng)絡(luò),連續(xù)兩次丟包的概率只有萬分之一,這個(gè)概率實(shí)在是太小了,忽略它比解決它更具性價(jià)比。

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:


任 TCP 虐我千百遍,我仍待 TCP 如初戀的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
达拉特旗| 五大连池市| 黄大仙区| 神农架林区| 新竹县| 平原县| 甘南县| 大化| 那曲县| 顺义区| 呼图壁县| 山东| 孝昌县| 蓬溪县| 汉川市| 星座| 祁东县| 宣化县| 黄山市| 始兴县| 兴义市| 新兴县| 比如县| 吉林省| 申扎县| 衡东县| 隆安县| 云林县| 定西市| 龙门县| 永川市| 柳河县| 普格县| 普兰县| 四子王旗| 图们市| 吉木乃县| 海丰县| 罗平县| 湟源县| 保靖县|