TCP

端口與進(jìn)程

TCP 的包是不包含 IP 地址信息的,那是 IP 層上的事,但是有源端口和目的端口。

就是說(shuō),端口這一東西,是屬于 TCP 知識(shí)范疇的。

我們知道兩個(gè)進(jìn)程,在計(jì)算機(jī)內(nèi)部進(jìn)行通信,可以有管道、內(nèi)存共享、信號(hào)量、消息隊(duì)列等方法。

而兩個(gè)進(jìn)程如果需要進(jìn)行通訊最基本的一個(gè)前提是能夠唯一的標(biāo)識(shí)一個(gè)進(jìn)程,在本地進(jìn)程通訊中我們可以使用「PID(進(jìn)程標(biāo)識(shí)符)」來(lái)唯一標(biāo)識(shí)一個(gè)進(jìn)程。

但 PID 只在本地唯一,如果把兩個(gè)進(jìn)程放到了不同的兩臺(tái)計(jì)算機(jī),然后他們要通信的話,PID 就不夠用了,這樣就需要另外一種手段了。

解決這個(gè)問(wèn)題的方法就是在運(yùn)輸層使用「協(xié)議端口號(hào) (protocol port number)」,簡(jiǎn)稱「端口 (port)」.

我們知道 IP 層的 ip 地址可以唯一標(biāo)識(shí)主機(jī),而 TCP 層協(xié)議和端口號(hào)可以唯一標(biāo)識(shí)主機(jī)的一個(gè)進(jìn)程,這樣我們可以利用:「ip地址+協(xié)議+端口號(hào)」唯一標(biāo)示網(wǎng)絡(luò)中的一個(gè)進(jìn)程。

在一些場(chǎng)合,也把這種唯一標(biāo)識(shí)的模式稱為「套接字 (Socket)」

這就是說(shuō),雖然通信的重點(diǎn)是應(yīng)用進(jìn)程,但我們只要把要傳送的報(bào)文交到目的主機(jī)的某一個(gè)合適的端口,剩下的工作就由 TCP 來(lái)完成了。

認(rèn)識(shí)端口

TCP 用一個(gè) 16 位端口號(hào)來(lái)標(biāo)識(shí)一個(gè)端口,可允許有 65536 ( 2的16次方) 個(gè)不同的端口號(hào),范圍在 0 ~ 65535 之間。

端口號(hào)根據(jù)服務(wù)器使用還是客戶端使用,以及常見(jiàn)不常見(jiàn)的維度來(lái)區(qū)分,主要有以下類別:

服務(wù)器端使用的端口號(hào)

熟知端口號(hào)

登記端口號(hào)

客戶端使用的端口號(hào)

下面展開(kāi)來(lái)說(shuō)說(shuō)。

端口號(hào)的分類

服務(wù)器端使用的端口號(hào)

熟知端口號(hào)

取值范圍:0 ~ 1023。

可以在 www.iana.org 查到,服務(wù)器機(jī)器一接通電源,服務(wù)器程序就運(yùn)行起來(lái),為了讓因特網(wǎng)上所有的客戶程序都能找到服務(wù)器程序,服務(wù)器程序所使用的端口就必須是固定的,并且總所眾所周知的。

一些常見(jiàn)的端口號(hào):

應(yīng)用程序FTPTELNETSMTPDNSTFTPHTTPHTTPSSNMP

熟知端口號(hào)212325536980443161

登記端口號(hào)

取值范圍:1024 ~ 49151。

這類端口沒(méi)有熟知的應(yīng)用程序使用,但是需要登記,以防重復(fù)

客戶端使用的端口號(hào)

取值范圍:49152 ~ 65535。

這類端口僅在客戶端進(jìn)程運(yùn)行時(shí)才動(dòng)態(tài)選擇。

又叫 短暫端口號(hào),表示這種端口的存在時(shí)間是短暫的,客戶進(jìn)程并不在意操作系統(tǒng)給它分配的是哪一個(gè)端口號(hào),因?yàn)榭蛻暨M(jìn)程之所以必須有一個(gè)端口號(hào),是為了讓傳輸層的實(shí)體能夠找到自己。

PS:在/etc/services文件中可以查看所有知名服務(wù)使用的端口。


TCP 是面向字節(jié)流的,但傳送的數(shù)據(jù)單元卻是報(bào)文段。

什么是報(bào)文?

例如一個(gè) 100kb 的 HTML 文檔需要傳送到另外一臺(tái)計(jì)算機(jī),并不會(huì)整個(gè)文檔直接傳送過(guò)去,可能會(huì)切割成幾個(gè)部分,比如四個(gè)分別為 25kb 的數(shù)據(jù)段。

而每個(gè)數(shù)據(jù)段再加上一個(gè) TCP 首部,就組成了 TCP 報(bào)文。

一共四個(gè) TCP 報(bào)文,發(fā)送到另外一個(gè)端。

另外一端收到數(shù)據(jù)包,然后再剔除 TCP 首部,組裝起來(lái)。

等到四個(gè)數(shù)據(jù)包都收到了,就能還原出來(lái)一個(gè)完整的 HTML 文檔了。

+

在 OSI 的七層協(xié)議中,第二層(數(shù)據(jù)鏈路層)的數(shù)據(jù)叫「Frame」,第三層(網(wǎng)絡(luò)層)上的數(shù)據(jù)叫「Packet」,第四層(傳輸層)的數(shù)據(jù)叫「Segment」。

TCP 報(bào)文 (Segment),包括首部和數(shù)據(jù)部分。

而 TCP 的全部功能都體現(xiàn)在它首部中各字段的作用,只有弄清 TCP 首部各字段的作用才能掌握 TCP 的工作原理。

TCP 報(bào)文段首部的前20個(gè)字節(jié)是固定的,后面有 4N 字節(jié)是根據(jù)需要而增加的。

下圖是把 TCP 報(bào)文中的首部放大來(lái)看。

TCP 的首部包括以下內(nèi)容:

源端口 source port

目的端口 destination port

序號(hào) sequence number

確認(rèn)號(hào) acknowledgment number

數(shù)據(jù)偏移 offset

保留 reserved

標(biāo)志位 tcp flags

窗口大小 window size

檢驗(yàn)和 checksum

緊急指針 urgent pointer

選項(xiàng) tcp options

下面展開(kāi)來(lái)描述個(gè)字段的意義和作用。

TCP 首部各字段的意義和作用

源端口和目的端口 Port

各占 2 個(gè) 字節(jié),共 4 個(gè)字節(jié)。

用來(lái)告知主機(jī)該報(bào)文段是來(lái)自哪里以及傳送給哪個(gè)應(yīng)用程序(應(yīng)用程序綁定了端口)的。

進(jìn)行 TCP 通訊時(shí),客戶端通常使用系統(tǒng)自動(dòng)選擇的臨時(shí)端口號(hào),而服務(wù)器則使用知名服務(wù)端口號(hào)。

序號(hào) Sequence Number

占 4 個(gè)字節(jié)。

TCP 是面向字節(jié)流的,在一個(gè) TCP 連接中傳輸?shù)淖止?jié)流中的每個(gè)字節(jié)都按照順序編號(hào)。

例如 100 kb 的 HTML 文檔數(shù)據(jù),一共 102400 (100 * 1024) 個(gè)字節(jié),那么每一個(gè)字節(jié)就都有了編號(hào),整個(gè)文檔的編號(hào)的范圍是 0 ~ 102399。

序號(hào)字段值指的是本報(bào)文段所發(fā)送的數(shù)據(jù)的第一個(gè)字節(jié)的序號(hào)。

那么 100 的 HTML 文檔分割成四個(gè)等分之后,

第一個(gè) TCP 報(bào)文段包含的是第一個(gè) 25kb 的數(shù)據(jù),0 ~ 25599 字節(jié), 該報(bào)文的序號(hào)的值就是:0

第二個(gè) TCP 報(bào)文段包含的是第二個(gè) 25kb 的數(shù)據(jù),25600 ~ 51199 字節(jié),該報(bào)文的序號(hào)的值就是:25600

......

根據(jù) 8 位 = 1 字節(jié),那么 4 個(gè)字節(jié)可以表示的數(shù)值范圍:[0, 2^32],一共 2^32 (4294967296) 個(gè)序號(hào)。

序號(hào)增加到最大值的時(shí)候,下一個(gè)序號(hào)又回到了 0.

也就是說(shuō) TCP 協(xié)議可對(duì) 4GB 的數(shù)據(jù)進(jìn)行編號(hào),在一般情況下可保證當(dāng)序號(hào)重復(fù)使用時(shí),舊序號(hào)的數(shù)據(jù)早已經(jīng)通過(guò)網(wǎng)絡(luò)到達(dá)終點(diǎn)或者丟失了。

確認(rèn)號(hào) Acknowledgemt Number

占 4 個(gè)字節(jié)。

表示期望收到對(duì)方下一個(gè)報(bào)文段的序號(hào)值。

TCP 的可靠性,是建立在「每一個(gè)數(shù)據(jù)報(bào)文都需要確認(rèn)收到」的基礎(chǔ)之上的。

就是說(shuō),通訊的任何一方在收到對(duì)方的一個(gè)報(bào)文之后,都要發(fā)送一個(gè)相對(duì)應(yīng)的「確認(rèn)報(bào)文」,來(lái)表達(dá)確認(rèn)收到。

那么,確認(rèn)報(bào)文,就會(huì)包含確認(rèn)號(hào)

例如,通訊的一方收到了第一個(gè) 25kb 的報(bào)文,該報(bào)文的 序號(hào)值=0,那么就需要回復(fù)一個(gè)確認(rèn)報(bào)文,其中的確認(rèn)號(hào) = 25600.

數(shù)據(jù)偏移 Offset

占 0.5 個(gè)字節(jié) (4 位)。

這個(gè)字段實(shí)際上是指出了TCP 報(bào)文段的首部長(zhǎng)度,它指出了 TCP報(bào)文段的數(shù)據(jù)起始處 距離 TCP報(bào)文的起始處 有多遠(yuǎn)。(注意 數(shù)據(jù)起始處 和 報(bào)文起始處 的意思)

一個(gè)數(shù)據(jù)偏移量 = 4 byte,由于 4 位二進(jìn)制數(shù)能表示的最大十進(jìn)制數(shù)字是 15,因此數(shù)據(jù)偏移的最大值是 60 byte,這也側(cè)面限制了 TCP 首部的最大長(zhǎng)度。

保留 Reserved

占 0.75 個(gè)字節(jié) (6 位)。

保留為今后使用,但目前應(yīng)置為 0。

標(biāo)志位 TCP Flags

標(biāo)志位,一共有 6 個(gè),分別占 1 位,共 6 位 。

每一位的值只有 0 和 1,分別表達(dá)不同意思。

緊急 URG (Urgent)

當(dāng) URG = 1 的時(shí)候,表示緊急指針(Urgent Pointer)有效。

它告訴系統(tǒng)此報(bào)文段中有緊急數(shù)據(jù),應(yīng)盡快傳送,而不要按原來(lái)的排隊(duì)順序來(lái)傳送。

URG 要與首部中的 緊急指針 字段配合使用。

確認(rèn) ACK (Acknowlegemt)

當(dāng) ACK = 1 的時(shí)候,確認(rèn)號(hào)(Acknowledgemt Number)有效。

一般稱攜帶 ACK 標(biāo)志的 TCP 報(bào)文段為「確認(rèn)報(bào)文段」。

TCP 規(guī)定,在連接建立后所有傳送的報(bào)文段都必須把 ACK 設(shè)置為 1。

推送 PSH (Push)

當(dāng) PSH = 1 的時(shí)候,表示該報(bào)文段高優(yōu)先級(jí),接收方 TCP 應(yīng)該盡快推送給接收應(yīng)用程序,而不用等到整個(gè) TCP 緩存都填滿了后再交付。

復(fù)位 RST (Reset)

當(dāng) RST = 1 的時(shí)候,表示 TCP 連接中出現(xiàn)嚴(yán)重錯(cuò)誤,需要釋放并重新建立連接。

一般稱攜帶 RST 標(biāo)志的 TCP 報(bào)文段為「復(fù)位報(bào)文段」。

同步 SYN (SYNchronization)

當(dāng) SYN = 1 的時(shí)候,表明這是一個(gè)請(qǐng)求連接報(bào)文段。

一般稱攜帶 SYN 標(biāo)志的 TCP 報(bào)文段為「同步報(bào)文段」。

在 TCP 三次握手中的第一個(gè)報(bào)文就是同步報(bào)文段,在連接建立時(shí)用來(lái)同步序號(hào)。

對(duì)方若同意建立連接,則應(yīng)在響應(yīng)的報(bào)文段中使 SYN = 1 和 ACK = 1。

終止 FIN (Finis)

當(dāng) FIN = 1 時(shí),表示此報(bào)文段的發(fā)送方的數(shù)據(jù)已經(jīng)發(fā)送完畢,并要求釋放 TCP 連接。

一般稱攜帶 FIN 的報(bào)文段為「結(jié)束報(bào)文段」。

在 TCP 四次揮手釋放連接的時(shí)候,就會(huì)用到該標(biāo)志。

窗口大小 Window Size

占 2 字節(jié)。

該字段明確指出了現(xiàn)在允許對(duì)方發(fā)送的數(shù)據(jù)量,它告訴對(duì)方本端的 TCP 接收緩沖區(qū)還能容納多少字節(jié)的數(shù)據(jù),這樣對(duì)方就可以控制發(fā)送數(shù)據(jù)的速度。

窗口大小的值是指,從本報(bào)文段首部中的確認(rèn)號(hào)算起,接收方目前允許對(duì)方發(fā)送的數(shù)據(jù)量。

例如,假如確認(rèn)號(hào)是 701 ,窗口字段是 1000。這就表明,從 701 號(hào)算起,發(fā)送此報(bào)文段的一方還有接收 1000 (字節(jié)序號(hào)是 701 ~ 1700) 個(gè)字節(jié)的數(shù)據(jù)的接收緩存空間。

校驗(yàn)和 TCP Checksum

占 2 個(gè)字節(jié)。

由發(fā)送端填充,接收端對(duì) TCP 報(bào)文段執(zhí)行 CRC 算法,以檢驗(yàn) TCP 報(bào)文段在傳輸過(guò)程中是否損壞,如果損壞這丟棄。

檢驗(yàn)范圍包括首部和數(shù)據(jù)兩部分,這也是 TCP 可靠傳輸?shù)囊粋€(gè)重要保障。

緊急指針 Urgent Pointer

占 2 個(gè)字節(jié)。

僅在 URG = 1 時(shí)才有意義,它指出本報(bào)文段中的緊急數(shù)據(jù)的字節(jié)數(shù)。

當(dāng) URG = 1 時(shí),發(fā)送方 TCP 就把緊急數(shù)據(jù)插入到本報(bào)文段數(shù)據(jù)的最前面,而在緊急數(shù)據(jù)后面的數(shù)據(jù)仍是普通數(shù)據(jù)。

因此,緊急指針指出了緊急數(shù)據(jù)的末尾在報(bào)文段中的位置。



TCP 的整個(gè)交流過(guò)程可以總結(jié)為:先建立連接,然后傳輸數(shù)據(jù),最后釋放鏈接。

三次握手,建立連接

TCP 連接建立要解決的首要問(wèn)題就是:要使每一方能夠確知對(duì)方的存在。

三次握手就像,在一個(gè)黑暗的森林,你知道前方十點(diǎn)鐘方向好像有人。

你喊了一句:Hello?I'am JerryC,Who are you?? ? ? ? ///發(fā)送連接

對(duì)面回了一句:Hi! I'am David, and nice to meet you!? ?///對(duì)方反應(yīng)

然后你回了一句:Nice to meet you too!? ? ? ? ? ? ? ? ? ? ? ? ///自己回應(yīng)

......(自此,你們才算真正認(rèn)識(shí)了雙方,開(kāi)始了后面省略3000字的談話)

所以說(shuō),兩個(gè)人需要交朋友(兩個(gè)端點(diǎn)需要建立連接),至少需要三次的通話(握手)

其實(shí),網(wǎng)絡(luò)上的傳輸是沒(méi)有連接的,TCP 也是一樣的。

而 TCP 所謂的「連接」,其實(shí)只不過(guò)是在通信的雙方維護(hù)一個(gè)「連接狀態(tài)」,讓它看上去好像有連接一樣。

連接建立過(guò)程

TCP 連接的建立采用客戶-服務(wù)器方式,

主動(dòng)發(fā)起連接建立的一方叫客戶端(Client),

被動(dòng)等待連接建立的一方叫服務(wù)器(Server)

最初的時(shí)候,兩端都處于CLOSED的狀態(tài),然后服務(wù)器打開(kāi)了 TCP 服務(wù),進(jìn)入LISTEN狀態(tài),監(jiān)聽(tīng)特定端口,等待客戶端的 TCP 請(qǐng)求。

第一次握手: 客戶端主動(dòng)打開(kāi)連接,發(fā)送 TCP 報(bào)文,進(jìn)行第一次握手,然后進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器發(fā)回確認(rèn)報(bào)文。

這時(shí)首部的同步位 SYN = 1,同時(shí)初始化一個(gè)序號(hào) Sequence Number = J。

TCP 規(guī)定,SYN 報(bào)文段不能攜帶數(shù)據(jù),但會(huì)消耗一個(gè)序號(hào)。

第二次握手: 服務(wù)器收到了 SYN 報(bào)文,如果同意建立連接,則向客戶端發(fā)送一個(gè)確認(rèn)報(bào)文,然后服務(wù)器進(jìn)入SYN_RCVD狀態(tài)。

這時(shí)首部的 SYN = 1,ACK = 1,而確認(rèn)號(hào) Acknowledgemt Number = J + 1,同時(shí)也為自己初始化一個(gè)序號(hào) Sequence Number = K。

這個(gè)報(bào)文同樣不攜帶數(shù)據(jù)。

第三次握手

客戶端收到了服務(wù)器發(fā)過(guò)來(lái)的確認(rèn)報(bào)文,還要向服務(wù)器給出確認(rèn),然后進(jìn)入ESTABLISHED狀態(tài)。

這時(shí)首部的 SYN 不再置為 1,而 ACK = 1,確認(rèn)號(hào) Acknowledgemt Number = K + 1,序號(hào) Sequence Number = J + 1。

第三次握手,一般會(huì)攜帶真正需要傳輸?shù)臄?shù)據(jù),當(dāng)服務(wù)器收到該數(shù)據(jù)報(bào)文的時(shí)候,就會(huì)同樣進(jìn)入ESTABLISHED狀態(tài)。 此時(shí),TCP 連接已經(jīng)建立。

對(duì)于建立連接的三次握手,主要目的是初始化序號(hào) Sequence Number,并且通信的雙方都需要告知對(duì)方自己的初始化序號(hào),所以這個(gè)過(guò)程也叫 SYN。

這個(gè)序號(hào)要作為以后的數(shù)據(jù)通信的序號(hào),以保證應(yīng)用層接收到的數(shù)據(jù)不會(huì)因?yàn)榫W(wǎng)絡(luò)上的傳輸問(wèn)題而亂序,因?yàn)門(mén)CP 會(huì)用這個(gè)序號(hào)來(lái)拼接數(shù)據(jù)。

利用連接設(shè)計(jì)缺陷實(shí)施 TCP Flood 攻擊

知道了 TCP 建立一個(gè)連接,需要進(jìn)行三次握手。

但如果你開(kāi)始思考「三次握手的必要性」的時(shí)候,就會(huì)知道,其實(shí)網(wǎng)絡(luò)是很復(fù)雜的,一個(gè)信息在途中丟失的可能性是有的。

如果數(shù)據(jù)丟失了,那么,就需要重新發(fā)送,這時(shí)候就要知道數(shù)據(jù)是否真的送達(dá)了。

這就是三次握手的必要性。

但是再向深一層思考,你給我發(fā)信息,我收到了,我回復(fù),因?yàn)槲沂蔷印?/p>

如果是小人,你給我發(fā)信息,我就算收到了,我也不回復(fù),你就一直等我著我的回復(fù)。

那么很多小人都這樣做,你就要一直記住你在等待著小人1號(hào)、小人2號(hào)、小人3號(hào)......直到你的腦容量爆棚,燒壞腦袋。

黑客就是利用這樣的設(shè)計(jì)缺陷,實(shí)施 TCP Flood 攻擊,屬于 DDOS 攻擊的一種。

想了解更多 SYN Flood 攻擊請(qǐng)看:SYN flood - wiki

四次揮手,釋放連接

TCP 有一個(gè)特別的概念叫做半關(guān)閉,這個(gè)概念是說(shuō),TCP 的連接是全雙工(可以同時(shí)發(fā)送和接收)的連接,因此在關(guān)閉連接的時(shí)候,必須關(guān)閉傳送和接收兩個(gè)方向上的連接。

客戶端給服務(wù)器發(fā)送一個(gè)攜帶 FIN 的 TCP 結(jié)束報(bào)文段,然后服務(wù)器返回給客戶端一個(gè) 確認(rèn)報(bào)文段,同時(shí)發(fā)送一個(gè) 結(jié)束報(bào)文段,當(dāng)客戶端回復(fù)一個(gè) 確認(rèn)報(bào)文段 之后,連接就結(jié)束了。

釋放連接過(guò)程

在結(jié)束之前,通信雙方都是處于ESTABLISHED狀態(tài),然后其中一方主動(dòng)斷開(kāi)連接。

下面假如客戶端先主動(dòng)斷開(kāi)連接。

第一次揮手:

客戶端向服務(wù)器發(fā)送結(jié)束報(bào)文段,然后進(jìn)入FIN_WAIT_1狀態(tài)。

此報(bào)文段 FIN = 1, Sequence Number = M。

第二次揮手:

服務(wù)端收到客戶端的結(jié)束報(bào)文段,然后發(fā)送確認(rèn)報(bào)文段,進(jìn)入CLOSE_WAIT狀態(tài)。

此報(bào)文段 ACK = 1, Sequence Number = M + 1。

客戶端收到該報(bào)文,會(huì)進(jìn)入FIN_WAIT_2狀態(tài)。

第三次揮手:

同時(shí)服務(wù)端向客戶端發(fā)送結(jié)束報(bào)文段,然后進(jìn)入LAST_ACK狀態(tài)。

此報(bào)文段 FIN = 1,Sequence Number = N。

第四次揮手:

客戶端收到服務(wù)端的結(jié)束報(bào)文段,然后發(fā)送確認(rèn)報(bào)文段,進(jìn)入TIME_WAIT狀態(tài),經(jīng)過(guò) 2MSL 之后,自動(dòng)進(jìn)入CLOSED狀態(tài)。

此報(bào)文段 ACK = 1, Sequence Number = N + 1。

服務(wù)端收到該報(bào)文之后,進(jìn)入CLOSED狀態(tài)。

關(guān)于 TIME_WAIT 過(guò)渡到 CLOSED 狀態(tài)說(shuō)明

TIME_WAIT進(jìn)入CLOSED需要經(jīng)過(guò) 2MSL,其中 MSL 就叫做 最長(zhǎng)報(bào)文段壽命(Maxinum Segment Lifetime),根據(jù) RFC 793 建議該值這是為 2 分鐘,也就是說(shuō)需要經(jīng)過(guò) 4 分鐘,才進(jìn)入CLOSED狀態(tài)。




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

無(wú)論客戶端還是服務(wù)器,在雙方 TCP 通訊的過(guò)程中,都會(huì)有著一個(gè)「狀態(tài)」的概念,狀態(tài)會(huì)隨著 TCP 通訊的不同階段而變化。

TCP 狀態(tài)流轉(zhuǎn)圖

各種狀態(tài)表示的意思

CLOSED:表示初始狀態(tài)

LISTEN:表示服務(wù)器端的某個(gè) socket 處于監(jiān)聽(tīng)狀態(tài),可以接受連接

SYN_SENT:在服務(wù)端監(jiān)聽(tīng)后,客戶端 socket 執(zhí)行 CONNECT 連接時(shí),客戶端發(fā)送 SYN 報(bào)文,此時(shí)客戶端就進(jìn)入 SYN_SENT 狀態(tài),等待服務(wù)端確認(rèn)。

SYN_RCVD:表示服務(wù)端接收到了 SYN 報(bào)文。

ESTABLISHED:表示連接已經(jīng)建立了。

FIN_WAIT_1:其中一方請(qǐng)求終止連接,等待對(duì)方的 FIN 報(bào)文。

FIN_WAIT_2:在FIN_WAIT_2之后, 當(dāng)對(duì)方回應(yīng) ACK 報(bào)文之后,進(jìn)入該狀態(tài)。

TIME_WAIT:表示收到了對(duì)方的 FIN 報(bào)文,并發(fā)送出了 ACK 報(bào)文,就等 2MSL 之后即可回到 CLOSED 狀態(tài)。

CLOSING:一種罕見(jiàn)狀態(tài),發(fā)生在發(fā)送 FIN 報(bào)文之后,本應(yīng)是先收到 ACK 報(bào)文,卻先收到對(duì)方的 FIN 報(bào)文,那么就從 FIN_WAIT_1 的狀態(tài)進(jìn)入 CLOSING 狀態(tài)。

CLOSE_WAIT:表示等待關(guān)閉,在 ESTABLISHED 過(guò)渡到 LAST_ACK 的一個(gè)過(guò)渡階段,該階段需要考慮是否還有數(shù)據(jù)發(fā)送給對(duì)方,如果沒(méi)有,就可以關(guān)閉連接,發(fā)送 FIN 報(bào)文,然后進(jìn)入 LAST_ACK 狀態(tài)。

LAST_ACK:被動(dòng)關(guān)閉一方發(fā)送 FIN 報(bào)文之后,最后等待對(duì)方的 ACK 報(bào)文所處的狀態(tài)。

CLOSED:當(dāng)收到 ACK 保溫后,就可以進(jìn)入 CLOSED 狀態(tài)了。




可靠性交付的實(shí)現(xiàn)

TCP 是一種提供可靠性交付的協(xié)議。

也就是說(shuō),通過(guò) TCP 連接傳輸?shù)臄?shù)據(jù),無(wú)差錯(cuò)、不丟失、不重復(fù)、并且按序到達(dá)。

但是在網(wǎng)絡(luò)中相連兩端之間的介質(zhì),是復(fù)雜的,并不確保數(shù)據(jù)的可靠性交付,那么 TCP 是怎么樣解決問(wèn)題的?

這就需要了解 TCP 的幾種技術(shù):

滑動(dòng)窗口

超時(shí)重傳

流量控制

擁塞控制

下面來(lái)分別講一下這幾種技術(shù)的實(shí)現(xiàn)原理。

超時(shí)重傳

重傳時(shí)機(jī)

TCP 報(bào)文段在傳輸?shù)倪^(guò)程中,下面的情況都是有可能發(fā)生的:

數(shù)據(jù)包中途丟失;

數(shù)據(jù)包順利到達(dá),但對(duì)方發(fā)送的 ACK 報(bào)文中途丟失;

數(shù)據(jù)包順利到達(dá),但對(duì)方異常未響應(yīng) ACK 或被對(duì)方丟棄;

當(dāng)出現(xiàn)這些異常情況時(shí),TCP 就會(huì)超時(shí)重傳。

TCP 每發(fā)送一個(gè)報(bào)文段,就對(duì)這個(gè)報(bào)文段設(shè)置一次計(jì)時(shí)器。只要計(jì)時(shí)器設(shè)置的重傳時(shí)間到了,但還沒(méi)有收到確認(rèn),就重傳這一報(bào)文段,這個(gè)就叫做「超時(shí)重傳」。

重傳算法

先認(rèn)識(shí)兩個(gè)概念

RTO ( Retransmission Time-Out ) 重傳超時(shí)時(shí)間

指發(fā)送端發(fā)送數(shù)據(jù)后、重傳數(shù)據(jù)前等待接收方收到該數(shù)據(jù) ACK 報(bào)文的時(shí)間。

大白話就是,需要等待多長(zhǎng)時(shí)間還沒(méi)收到確認(rèn),就重新傳一次。

RTO 的設(shè)置對(duì)于重傳非常重要:

設(shè)長(zhǎng)了,重發(fā)就慢,沒(méi)有效率,性能差;

設(shè)短了,重發(fā)得就快,會(huì)增加網(wǎng)絡(luò)擁塞,導(dǎo)致更多的超時(shí),更多的超時(shí)導(dǎo)致更多的重發(fā)。

RTT ( Round Trip Time ) 連接往返時(shí)間

指發(fā)送端從發(fā)送 TCP 包開(kāi)始到接收它的 ACK 報(bào)文之間所耗費(fèi)的時(shí)間。

而在實(shí)際的網(wǎng)絡(luò)傳輸中,RTT 的值每次都是隨機(jī)的,無(wú)法事先預(yù)預(yù)知。

TCP 通過(guò)測(cè)量來(lái)獲得連接當(dāng)前 RTT 的一個(gè)估計(jì)值,并以該 RTT 估計(jì)值為基準(zhǔn)來(lái)設(shè)置當(dāng)前的 RTO。

這就引入了一類算法的稱呼:自適應(yīng)重傳算法(Adaptive Restransmission Algorithm)

這類算法的關(guān)鍵就在于對(duì)當(dāng)前 RTT 的準(zhǔn)確估計(jì),以便適時(shí)調(diào)整 RTO。

關(guān)于自適應(yīng)重傳算法,經(jīng)歷過(guò)多次的迭代和修正。

從 1981 年的RFC793提及的經(jīng)典算法,到 1987 年 Karn 提出的 Karn/Partridge 算法,再到后來(lái)的 1988 年的 Jacobson / Karels 算法。

最后的這個(gè)算法在被用在今天的 TCP 協(xié)議中(Linux的源代碼在:tcp_rtt_estimator)。

自適應(yīng)重傳算法的發(fā)展讀者有興趣可以參考其他資料,在這里我拎一個(gè)現(xiàn)在在用的算法出來(lái)講講,隨意感受一下。

Jacobson / Karels 算法

1988年,有人推出來(lái)了一個(gè)新的算法,這個(gè)算法叫 Jacobson / Karels Algorithm(參看RFC6298)。

其計(jì)算公式:

SRTT = SRTT + α ( RTT – SRTT ) —— 計(jì)算平滑 RTT

DevRTT = ( 1-β )DevRTT + β( | RTT - SRTT | ) ——計(jì)算平滑 RTT 和真實(shí)的差距(加權(quán)移動(dòng)平均)

RTO= μSRTT + ?DevRTT

其中:

α、β、μ、?是可以調(diào)整的參數(shù),在 RFC6298 中給出了對(duì)應(yīng)的參考值,而在Linux下,α = 0.125,β = 0.25, μ = 1,? = 4;

SRTT 是 Smoothed RTT 的意思,是 RTT 的平滑計(jì)算值,即根據(jù)每次測(cè)量的 RTT 和舊的 RTT 進(jìn)行運(yùn)算,得出新的 RTT。SRTT 的值,會(huì)在每一次測(cè)量到 RTT 之后進(jìn)行更新;

DevRTT 是 Deviation RTT 的意思,根據(jù)每次測(cè)量的 RTT 和舊的 SRTT 值進(jìn)行運(yùn)算,得出新的 DevRTT;

由算法可以知道 RTO 的值會(huì)根據(jù)每次測(cè)量的 RTT 值變化而變化,基本要點(diǎn)是 TCP 監(jiān)視每個(gè)連接的性能,由每一個(gè) TCP 的連接情況推算出合適的 RTO 值,根據(jù)不同的網(wǎng)絡(luò)情況,自動(dòng)修改 RTO 值,以適應(yīng)負(fù)責(zé)的網(wǎng)絡(luò)變化。

擁塞控制

慢啟動(dòng)(Slow Start) 與 擁塞避免(Congestion Avoidance)

[ ] TODO

快速重傳(Fast Retransmit) 與 快速恢復(fù)(Fast Recovery)

[ ] TODO

滑動(dòng)窗口 Sliding Window

滑動(dòng)窗口協(xié)議比較復(fù)雜,也是 TCP 協(xié)議的精髓所在。

TCP 頭里有一個(gè)字段叫 Window,叫 Advertised-Window,這個(gè)字段是接收端告訴發(fā)送端自己還有多少緩沖區(qū)可以接收數(shù)據(jù)。于是發(fā)送端就可以根據(jù)這個(gè)接收端的處理能力來(lái)發(fā)送數(shù)據(jù),而不會(huì)導(dǎo)致接收端處理不過(guò)來(lái)。

滑動(dòng)窗口分為「接收窗口」和「發(fā)送窗口」

因?yàn)?TCP 協(xié)議是全雙工的,會(huì)話的雙方都可以同時(shí)接收和發(fā)送,那么就需要各自維護(hù)一個(gè)「發(fā)送窗口」和「接收窗口」。

發(fā)送窗口

大小取決于對(duì)端通告的接受窗口。

只有收到對(duì)端對(duì)于本端發(fā)送窗口內(nèi)字節(jié)的 ACK 確認(rèn),才會(huì)移動(dòng)發(fā)送窗口的左邊界。

下圖是發(fā)送窗口的示意圖:

對(duì)于發(fā)送窗口,在緩存內(nèi)的數(shù)據(jù)有四種狀態(tài):

#1 已發(fā)送,并得到接收方 ACK 確認(rèn);

#2 已發(fā)送,但還未收到接收方 ACK;

#3 未發(fā)送,但接收方允許發(fā)送,接收方還有空間

#4 未發(fā)送,且接收方不允許發(fā)送,接收方?jīng)]有空間

如果下一刻,收到了接收方對(duì)于 32-36 字節(jié)序的數(shù)據(jù)包的 ACK 確認(rèn),那么發(fā)送方的窗口就會(huì)發(fā)生「滑動(dòng)」。

并且發(fā)送下一個(gè) 46-51 字節(jié)序的數(shù)據(jù)包。

滑動(dòng)窗口的概念,描述了 TCP 的數(shù)據(jù)是怎么發(fā)送,以及怎么接收的。

TCP 的滑動(dòng)窗口是動(dòng)態(tài)的,我們可以想象成小學(xué)常見(jiàn)的一個(gè)數(shù)學(xué)題,一個(gè)水池,體積 V,每小時(shí)進(jìn)水量 V1, 出水量 V2。

當(dāng)水池滿了就不允許再注入了,如果有個(gè)液壓系統(tǒng)控制水池大小,那么就可以控制水的注入速率和量了。

應(yīng)用程序可以根據(jù)自身的處理能力變化,通過(guò) API 來(lái)控制本端 TCP 接收窗口的大小,來(lái)進(jìn)行流量控制。

接收窗口

大小取決于應(yīng)用、系統(tǒng)、硬件的限制。

下圖是接收窗口的示意圖(找不到圖,唯有自己畫(huà)了):

相對(duì)于發(fā)送窗口,接受窗口在緩存內(nèi)的數(shù)據(jù)只有三種狀態(tài):

已接收已確認(rèn);

未接收,準(zhǔn)備接收;

未接收,并未準(zhǔn)備接收;

下一刻接收到來(lái)自發(fā)送端的 32-36 數(shù)據(jù)包,然后回送 ACK 確認(rèn)報(bào),并且移動(dòng)接收窗口。

另外接收端相對(duì)于發(fā)送端還有不同的一點(diǎn),只有前面所有的段都確認(rèn)的情況下才會(huì)移動(dòng)左邊界,

在前面還有字節(jié)未接收但收到后面字節(jié)的情況下,窗口不會(huì)移動(dòng),并不對(duì)后續(xù)字節(jié)確認(rèn),以此確保對(duì)端會(huì)對(duì)這些數(shù)據(jù)重傳。

假如 32-36 字節(jié)不是一個(gè)報(bào)文段的,而是每個(gè)字節(jié)一個(gè)報(bào)文段的話,那么就會(huì)分成了 5 個(gè)報(bào)文段。

在實(shí)際的網(wǎng)絡(luò)環(huán)境中,不能確保是按序收到的,其中會(huì)有一些早達(dá)到,一些遲到達(dá)。

如圖中的 34、35 字節(jié)序,先收到了,接收窗口也不會(huì)移動(dòng)。

因?yàn)橛锌赡?32、33 字節(jié)序會(huì)出現(xiàn)丟包或者超時(shí),這時(shí)就需要發(fā)送端重發(fā)報(bào)文段了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 21.1 引言 TCP提供可靠的運(yùn)輸層。它使用的方法之一就是確認(rèn)從另一端收到的數(shù)據(jù)。但數(shù)據(jù)和確認(rèn)都有可能會(huì)丟失。T...
    張芳濤閱讀 3,377評(píng)論 0 8
  • 個(gè)人認(rèn)為,Goodboy1881先生的TCP /IP 協(xié)議詳解學(xué)習(xí)博客系列博客是一部非常精彩的學(xué)習(xí)筆記,這雖然只是...
    貳零壹柒_fc10閱讀 5,192評(píng)論 0 8
  • 1.這篇文章不是本人原創(chuàng)的,只是個(gè)人為了對(duì)這部分知識(shí)做一個(gè)整理和系統(tǒng)的輸出而編輯成的,在此鄭重地向本文所引用文章的...
    SOMCENT閱讀 13,353評(píng)論 6 174
  • 20.1 引言 在第15章我們看到TFTP使用了停止等待協(xié)議。數(shù)據(jù)發(fā)送方在發(fā)送下一個(gè)數(shù)據(jù)塊之前需要等待接收對(duì)已發(fā)送...
    張芳濤閱讀 932評(píng)論 0 2
  • 套接字選項(xiàng)SO_RESUEADDR 即使端口處于2MSL狀態(tài),使用該選項(xiàng),仍然能夠在該端口建立連接。服務(wù)器常會(huì)設(shè)置...
    Myth52125閱讀 1,519評(píng)論 0 0

友情鏈接更多精彩內(nèi)容