端口與進(jìn)程
TCP 的包是不包含 IP 地址信息的味榛,那是 IP 層上的事急前,但是有源端口和目的端口鲸伴。
就是說塘雳,端口這一東西施绎,是屬于 TCP 知識范疇的触创。
我們知道兩個進(jìn)程颇蜡,在計(jì)算機(jī)內(nèi)部進(jìn)行通信产上,可以有管道撑蒜、內(nèi)存共享歹啼、信號量、消息隊(duì)列等方法减江。
而兩個進(jìn)程如果需要進(jìn)行通訊最基本的一個前提是能夠唯一的標(biāo)識一個進(jìn)程染突,在本地進(jìn)程通訊中我們可以使用「PID(進(jìn)程標(biāo)識符)」來唯一標(biāo)識一個進(jìn)程。
但 PID 只在本地唯一辈灼,如果把兩個進(jìn)程放到了不同的兩臺計(jì)算機(jī)份企,然后他們要通信的話,PID 就不夠用了巡莹,這樣就需要另外一種手段了司志。
解決這個問題的方法就是在運(yùn)輸層使用「協(xié)議端口號 (protocol port number)」,簡稱「端口 (port)」.
我們知道 IP 層的 ip 地址可以唯一標(biāo)識主機(jī)降宅,而 TCP 層協(xié)議和端口號可以唯一標(biāo)識主機(jī)的一個進(jìn)程骂远,這樣我們可以利用:「ip地址+協(xié)議+端口號」唯一標(biāo)示網(wǎng)絡(luò)中的一個進(jìn)程。
在一些場合腰根,也把這種唯一標(biāo)識的模式稱為「套接字 (Socket)」
這就是說激才,雖然通信的重點(diǎn)是應(yīng)用進(jìn)程,但我們只要把要傳送的報(bào)文交到目的主機(jī)的某一個合適的端口额嘿,剩下的工作就由 TCP 來完成了瘸恼。
認(rèn)識端口
TCP 用一個 16 位端口號來標(biāo)識一個端口,可允許有 65536 ( 2的16次方) 個不同的端口號册养,范圍在 0 ~ 65535 之間东帅。
端口號根據(jù)服務(wù)器使用還是客戶端使用,以及常見不常見的維度來區(qū)分球拦,主要有以下類別:
服務(wù)器端使用的端口號
熟知端口號
登記端口號
客戶端使用的端口號
下面展開來說說靠闭。
端口號的分類
服務(wù)器端使用的端口號
熟知端口號:
取值范圍:0 ~ 1023帐我。
可以在 www.iana.org 查到,服務(wù)器機(jī)器一接通電源愧膀,服務(wù)器程序就運(yùn)行起來拦键,為了讓因特網(wǎng)上所有的客戶程序都能找到服務(wù)器程序,服務(wù)器程序所使用的端口就必須是固定的扇调,并且總所眾所周知的矿咕。
一些常見的端口號:
應(yīng)用程序FTPTELNETSMTPDNSTFTPHTTPHTTPSSNMP
熟知端口號212325536980443161
登記端口號:
取值范圍:1024 ~ 49151。
這類端口沒有熟知的應(yīng)用程序使用狼钮,但是需要登記碳柱,以防重復(fù)
客戶端使用的端口號
取值范圍:49152 ~ 65535。
這類端口僅在客戶端進(jìn)程運(yùn)行時才動態(tài)選擇熬芜。
又叫 短暫端口號莲镣,表示這種端口的存在時間是短暫的,客戶進(jìn)程并不在意操作系統(tǒng)給它分配的是哪一個端口號涎拉,因?yàn)榭蛻暨M(jìn)程之所以必須有一個端口號瑞侮,是為了讓傳輸層的實(shí)體能夠找到自己。
PS:在/etc/services文件中可以查看所有知名服務(wù)使用的端口鼓拧。
TCP 是面向字節(jié)流的半火,但傳送的數(shù)據(jù)單元卻是報(bào)文段。
什么是報(bào)文季俩?
例如一個 100kb 的 HTML 文檔需要傳送到另外一臺計(jì)算機(jī)钮糖,并不會整個文檔直接傳送過去,可能會切割成幾個部分酌住,比如四個分別為 25kb 的數(shù)據(jù)段店归。
而每個數(shù)據(jù)段再加上一個 TCP 首部,就組成了 TCP 報(bào)文酪我。
一共四個 TCP 報(bào)文消痛,發(fā)送到另外一個端。
另外一端收到數(shù)據(jù)包都哭,然后再剔除 TCP 首部秩伞,組裝起來。
等到四個數(shù)據(jù)包都收到了欺矫,就能還原出來一個完整的 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個字節(jié)是固定的捏鱼,后面有 4N 字節(jié)是根據(jù)需要而增加的。
下圖是把 TCP 報(bào)文中的首部放大來看酪耕。
TCP 的首部包括以下內(nèi)容:
源端口 source port
目的端口 destination port
序號 sequence number
確認(rèn)號 acknowledgment number
數(shù)據(jù)偏移 offset
保留 reserved
標(biāo)志位 tcp flags
窗口大小 window size
檢驗(yàn)和 checksum
緊急指針 urgent pointer
選項(xiàng) tcp options
下面展開來描述個字段的意義和作用导梆。
TCP 首部各字段的意義和作用
源端口和目的端口 Port
各占 2 個 字節(jié),共 4 個字節(jié)迂烁。
用來告知主機(jī)該報(bào)文段是來自哪里以及傳送給哪個應(yīng)用程序(應(yīng)用程序綁定了端口)的看尼。
進(jìn)行 TCP 通訊時,客戶端通常使用系統(tǒng)自動選擇的臨時端口號盟步,而服務(wù)器則使用知名服務(wù)端口號藏斩。
序號 Sequence Number
占 4 個字節(jié)。
TCP 是面向字節(jié)流的却盘,在一個 TCP 連接中傳輸?shù)淖止?jié)流中的每個字節(jié)都按照順序編號狰域。
例如 100 kb 的 HTML 文檔數(shù)據(jù),一共 102400 (100 * 1024) 個字節(jié)黄橘,那么每一個字節(jié)就都有了編號兆览,整個文檔的編號的范圍是 0 ~ 102399。
序號字段值指的是本報(bào)文段所發(fā)送的數(shù)據(jù)的第一個字節(jié)的序號塞关。
那么 100 的 HTML 文檔分割成四個等分之后抬探,
第一個 TCP 報(bào)文段包含的是第一個 25kb 的數(shù)據(jù),0 ~ 25599 字節(jié)描孟, 該報(bào)文的序號的值就是:0
第二個 TCP 報(bào)文段包含的是第二個 25kb 的數(shù)據(jù)驶睦,25600 ~ 51199 字節(jié),該報(bào)文的序號的值就是:25600
......
根據(jù) 8 位 = 1 字節(jié)匿醒,那么 4 個字節(jié)可以表示的數(shù)值范圍:[0, 2^32]场航,一共 2^32 (4294967296) 個序號。
序號增加到最大值的時候廉羔,下一個序號又回到了 0.
也就是說 TCP 協(xié)議可對 4GB 的數(shù)據(jù)進(jìn)行編號溉痢,在一般情況下可保證當(dāng)序號重復(fù)使用時,舊序號的數(shù)據(jù)早已經(jīng)通過網(wǎng)絡(luò)到達(dá)終點(diǎn)或者丟失了憋他。
確認(rèn)號 Acknowledgemt Number
占 4 個字節(jié)孩饼。
表示期望收到對方下一個報(bào)文段的序號值。
TCP 的可靠性竹挡,是建立在「每一個數(shù)據(jù)報(bào)文都需要確認(rèn)收到」的基礎(chǔ)之上的镀娶。
就是說,通訊的任何一方在收到對方的一個報(bào)文之后揪罕,都要發(fā)送一個相對應(yīng)的「確認(rèn)報(bào)文」梯码,來表達(dá)確認(rèn)收到宝泵。
那么,確認(rèn)報(bào)文轩娶,就會包含確認(rèn)號儿奶。
例如,通訊的一方收到了第一個 25kb 的報(bào)文鳄抒,該報(bào)文的 序號值=0闯捎,那么就需要回復(fù)一個確認(rèn)報(bào)文,其中的確認(rèn)號 = 25600.
數(shù)據(jù)偏移 Offset
占 0.5 個字節(jié) (4 位)许溅。
這個字段實(shí)際上是指出了TCP 報(bào)文段的首部長度瓤鼻,它指出了 TCP報(bào)文段的數(shù)據(jù)起始處 距離 TCP報(bào)文的起始處 有多遠(yuǎn)。(注意 數(shù)據(jù)起始處 和 報(bào)文起始處 的意思)
一個數(shù)據(jù)偏移量 = 4 byte闹司,由于 4 位二進(jìn)制數(shù)能表示的最大十進(jìn)制數(shù)字是 15娱仔,因此數(shù)據(jù)偏移的最大值是 60 byte,這也側(cè)面限制了 TCP 首部的最大長度游桩。
保留 Reserved
占 0.75 個字節(jié) (6 位)牲迫。
保留為今后使用,但目前應(yīng)置為 0借卧。
標(biāo)志位 TCP Flags
標(biāo)志位盹憎,一共有 6 個,分別占 1 位铐刘,共 6 位 陪每。
每一位的值只有 0 和 1,分別表達(dá)不同意思镰吵。
緊急 URG (Urgent)
當(dāng) URG = 1 的時候檩禾,表示緊急指針(Urgent Pointer)有效。
它告訴系統(tǒng)此報(bào)文段中有緊急數(shù)據(jù)疤祭,應(yīng)盡快傳送盼产,而不要按原來的排隊(duì)順序來傳送。
URG 要與首部中的 緊急指針 字段配合使用勺馆。
確認(rèn) ACK (Acknowlegemt)
當(dāng) ACK = 1 的時候戏售,確認(rèn)號(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 的時候悲柱,表示該報(bào)文段高優(yōu)先級锋喜,接收方 TCP 應(yīng)該盡快推送給接收應(yīng)用程序,而不用等到整個 TCP 緩存都填滿了后再交付豌鸡。
復(fù)位 RST (Reset)
當(dāng) RST = 1 的時候嘿般,表示 TCP 連接中出現(xiàn)嚴(yán)重錯誤轴总,需要釋放并重新建立連接。
一般稱攜帶 RST 標(biāo)志的 TCP 報(bào)文段為「復(fù)位報(bào)文段」博个。
同步 SYN (SYNchronization)
當(dāng) SYN = 1 的時候,表明這是一個請求連接報(bào)文段功偿。
一般稱攜帶 SYN 標(biāo)志的 TCP 報(bào)文段為「同步報(bào)文段」盆佣。
在 TCP 三次握手中的第一個報(bào)文就是同步報(bào)文段,在連接建立時用來同步序號械荷。
對方若同意建立連接共耍,則應(yīng)在響應(yīng)的報(bào)文段中使 SYN = 1 和 ACK = 1。
終止 FIN (Finis)
當(dāng) FIN = 1 時吨瞎,表示此報(bào)文段的發(fā)送方的數(shù)據(jù)已經(jīng)發(fā)送完畢痹兜,并要求釋放 TCP 連接。
一般稱攜帶 FIN 的報(bào)文段為「結(jié)束報(bào)文段」颤诀。
在 TCP 四次揮手釋放連接的時候字旭,就會用到該標(biāo)志。
窗口大小 Window Size
占 2 字節(jié)崖叫。
該字段明確指出了現(xiàn)在允許對方發(fā)送的數(shù)據(jù)量遗淳,它告訴對方本端的 TCP 接收緩沖區(qū)還能容納多少字節(jié)的數(shù)據(jù),這樣對方就可以控制發(fā)送數(shù)據(jù)的速度心傀。
窗口大小的值是指屈暗,從本報(bào)文段首部中的確認(rèn)號算起,接收方目前允許對方發(fā)送的數(shù)據(jù)量脂男。
例如养叛,假如確認(rèn)號是 701 ,窗口字段是 1000宰翅。這就表明弃甥,從 701 號算起,發(fā)送此報(bào)文段的一方還有接收 1000 (字節(jié)序號是 701 ~ 1700) 個字節(jié)的數(shù)據(jù)的接收緩存空間堕油。
校驗(yàn)和 TCP Checksum
占 2 個字節(jié)潘飘。
由發(fā)送端填充,接收端對 TCP 報(bào)文段執(zhí)行 CRC 算法掉缺,以檢驗(yàn) TCP 報(bào)文段在傳輸過程中是否損壞卜录,如果損壞這丟棄。
檢驗(yàn)范圍包括首部和數(shù)據(jù)兩部分眶明,這也是 TCP 可靠傳輸?shù)囊粋€重要保障艰毒。
緊急指針 Urgent Pointer
占 2 個字節(jié)。
僅在 URG = 1 時才有意義搜囱,它指出本報(bào)文段中的緊急數(shù)據(jù)的字節(jié)數(shù)丑瞧。
當(dāng) URG = 1 時柑土,發(fā)送方 TCP 就把緊急數(shù)據(jù)插入到本報(bào)文段數(shù)據(jù)的最前面,而在緊急數(shù)據(jù)后面的數(shù)據(jù)仍是普通數(shù)據(jù)绊汹。
因此稽屏,緊急指針指出了緊急數(shù)據(jù)的末尾在報(bào)文段中的位置。
TCP 的整個交流過程可以總結(jié)為:先建立連接西乖,然后傳輸數(shù)據(jù)狐榔,最后釋放鏈接。
三次握手获雕,建立連接
TCP 連接建立要解決的首要問題就是:要使每一方能夠確知對方的存在薄腻。
三次握手就像,在一個黑暗的森林届案,你知道前方十點(diǎn)鐘方向好像有人庵楷。
你喊了一句:Hello?I'am JerryC楣颠,Who are you尽纽?? ? ? ? ///發(fā)送連接
對面回了一句:Hi! I'am David, and nice to meet you!? ?///對方反應(yīng)
然后你回了一句:Nice to meet you too!? ? ? ? ? ? ? ? ? ? ? ? ///自己回應(yīng)
......(自此,你們才算真正認(rèn)識了雙方童漩,開始了后面省略3000字的談話)
所以說蜓斧,兩個人需要交朋友(兩個端點(diǎn)需要建立連接),至少需要三次的通話(握手)
其實(shí)睁冬,網(wǎng)絡(luò)上的傳輸是沒有連接的挎春,TCP 也是一樣的。
而 TCP 所謂的「連接」豆拨,其實(shí)只不過是在通信的雙方維護(hù)一個「連接狀態(tài)」直奋,讓它看上去好像有連接一樣。
連接建立過程
TCP 連接的建立采用客戶-服務(wù)器方式施禾,
主動發(fā)起連接建立的一方叫客戶端(Client)脚线,
被動等待連接建立的一方叫服務(wù)器(Server)。
最初的時候弥搞,兩端都處于CLOSED的狀態(tài)邮绿,然后服務(wù)器打開了 TCP 服務(wù),進(jìn)入LISTEN狀態(tài)攀例,監(jiān)聽特定端口船逮,等待客戶端的 TCP 請求。
第一次握手: 客戶端主動打開連接粤铭,發(fā)送 TCP 報(bào)文挖胃,進(jìn)行第一次握手,然后進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器發(fā)回確認(rèn)報(bào)文酱鸭。
這時首部的同步位 SYN = 1吗垮,同時初始化一個序號 Sequence Number = J。
TCP 規(guī)定凹髓,SYN 報(bào)文段不能攜帶數(shù)據(jù)烁登,但會消耗一個序號。
第二次握手: 服務(wù)器收到了 SYN 報(bào)文蔚舀,如果同意建立連接防泵,則向客戶端發(fā)送一個確認(rèn)報(bào)文,然后服務(wù)器進(jìn)入SYN_RCVD狀態(tài)蝗敢。
這時首部的 SYN = 1,ACK = 1足删,而確認(rèn)號 Acknowledgemt Number = J + 1寿谴,同時也為自己初始化一個序號 Sequence Number = K。
這個報(bào)文同樣不攜帶數(shù)據(jù)失受。
第三次握手:
客戶端收到了服務(wù)器發(fā)過來的確認(rèn)報(bào)文讶泰,還要向服務(wù)器給出確認(rèn),然后進(jìn)入ESTABLISHED狀態(tài)拂到。
這時首部的 SYN 不再置為 1痪署,而 ACK = 1,確認(rèn)號 Acknowledgemt Number = K + 1兄旬,序號 Sequence Number = J + 1狼犯。
第三次握手,一般會攜帶真正需要傳輸?shù)臄?shù)據(jù)领铐,當(dāng)服務(wù)器收到該數(shù)據(jù)報(bào)文的時候悯森,就會同樣進(jìn)入ESTABLISHED狀態(tài)。 此時绪撵,TCP 連接已經(jīng)建立瓢姻。
對于建立連接的三次握手,主要目的是初始化序號 Sequence Number音诈,并且通信的雙方都需要告知對方自己的初始化序號幻碱,所以這個過程也叫 SYN。
這個序號要作為以后的數(shù)據(jù)通信的序號细溅,以保證應(yīng)用層接收到的數(shù)據(jù)不會因?yàn)榫W(wǎng)絡(luò)上的傳輸問題而亂序褥傍,因?yàn)門CP 會用這個序號來拼接數(shù)據(jù)。
利用連接設(shè)計(jì)缺陷實(shí)施 TCP Flood 攻擊
知道了 TCP 建立一個連接喇聊,需要進(jìn)行三次握手摔桦。
但如果你開始思考「三次握手的必要性」的時候,就會知道,其實(shí)網(wǎng)絡(luò)是很復(fù)雜的邻耕,一個信息在途中丟失的可能性是有的鸥咖。
如果數(shù)據(jù)丟失了,那么兄世,就需要重新發(fā)送啼辣,這時候就要知道數(shù)據(jù)是否真的送達(dá)了。
這就是三次握手的必要性御滩。
但是再向深一層思考鸥拧,你給我發(fā)信息,我收到了削解,我回復(fù)富弦,因?yàn)槲沂蔷印?/p>
如果是小人,你給我發(fā)信息氛驮,我就算收到了腕柜,我也不回復(fù),你就一直等我著我的回復(fù)矫废。
那么很多小人都這樣做盏缤,你就要一直記住你在等待著小人1號、小人2號蓖扑、小人3號......直到你的腦容量爆棚唉铜,燒壞腦袋。
黑客就是利用這樣的設(shè)計(jì)缺陷律杠,實(shí)施 TCP Flood 攻擊潭流,屬于 DDOS 攻擊的一種。
想了解更多 SYN Flood 攻擊請看:SYN flood - wiki
四次揮手柜去,釋放連接
TCP 有一個特別的概念叫做半關(guān)閉幻枉,這個概念是說,TCP 的連接是全雙工(可以同時發(fā)送和接收)的連接诡蜓,因此在關(guān)閉連接的時候熬甫,必須關(guān)閉傳送和接收兩個方向上的連接。
客戶端給服務(wù)器發(fā)送一個攜帶 FIN 的 TCP 結(jié)束報(bào)文段蔓罚,然后服務(wù)器返回給客戶端一個 確認(rèn)報(bào)文段椿肩,同時發(fā)送一個 結(jié)束報(bào)文段,當(dāng)客戶端回復(fù)一個 確認(rèn)報(bào)文段 之后豺谈,連接就結(jié)束了郑象。
釋放連接過程
在結(jié)束之前,通信雙方都是處于ESTABLISHED狀態(tà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)文,會進(jìn)入FIN_WAIT_2狀態(tài)担映。
第三次揮手:
同時服務(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)過 2MSL 之后拔妥,自動進(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 過渡到 CLOSED 狀態(tài)說明:
從TIME_WAIT進(jìn)入CLOSED需要經(jīng)過 2MSL,其中 MSL 就叫做 最長報(bào)文段壽命(Maxinum Segment Lifetime)硬纤,根據(jù) RFC 793 建議該值這是為 2 分鐘解滓,也就是說需要經(jīng)過 4 分鐘,才進(jìn)入CLOSED狀態(tài)筝家。
狀態(tài)流轉(zhuǎn)
無論客戶端還是服務(wù)器洼裤,在雙方 TCP 通訊的過程中,都會有著一個「狀態(tài)」的概念溪王,狀態(tài)會隨著 TCP 通訊的不同階段而變化腮鞍。
TCP 狀態(tài)流轉(zhuǎn)圖
各種狀態(tài)表示的意思
CLOSED:表示初始狀態(tài)
LISTEN:表示服務(wù)器端的某個 socket 處于監(jiān)聽狀態(tài),可以接受連接
SYN_SENT:在服務(wù)端監(jiān)聽后莹菱,客戶端 socket 執(zhí)行 CONNECT 連接時移国,客戶端發(fā)送 SYN 報(bào)文,此時客戶端就進(jìn)入 SYN_SENT 狀態(tài)道伟,等待服務(wù)端確認(rèn)迹缀。
SYN_RCVD:表示服務(wù)端接收到了 SYN 報(bào)文。
ESTABLISHED:表示連接已經(jīng)建立了。
FIN_WAIT_1:其中一方請求終止連接祝懂,等待對方的 FIN 報(bào)文票摇。
FIN_WAIT_2:在FIN_WAIT_2之后, 當(dāng)對方回應(yīng) ACK 報(bào)文之后嫂易,進(jìn)入該狀態(tài)兄朋。
TIME_WAIT:表示收到了對方的 FIN 報(bào)文,并發(fā)送出了 ACK 報(bào)文怜械,就等 2MSL 之后即可回到 CLOSED 狀態(tài)颅和。
CLOSING:一種罕見狀態(tài),發(fā)生在發(fā)送 FIN 報(bào)文之后缕允,本應(yīng)是先收到 ACK 報(bào)文峡扩,卻先收到對方的 FIN 報(bào)文,那么就從 FIN_WAIT_1 的狀態(tài)進(jìn)入 CLOSING 狀態(tài)障本。
CLOSE_WAIT:表示等待關(guān)閉教届,在 ESTABLISHED 過渡到 LAST_ACK 的一個過渡階段,該階段需要考慮是否還有數(shù)據(jù)發(fā)送給對方驾霜,如果沒有案训,就可以關(guān)閉連接,發(fā)送 FIN 報(bào)文粪糙,然后進(jìn)入 LAST_ACK 狀態(tài)强霎。
LAST_ACK:被動關(guān)閉一方發(fā)送 FIN 報(bào)文之后,最后等待對方的 ACK 報(bào)文所處的狀態(tài)蓉冈。
CLOSED:當(dāng)收到 ACK 保溫后城舞,就可以進(jìn)入 CLOSED 狀態(tài)了。
可靠性交付的實(shí)現(xiàn)
TCP 是一種提供可靠性交付的協(xié)議寞酿。
也就是說家夺,通過 TCP 連接傳輸?shù)臄?shù)據(jù),無差錯伐弹、不丟失拉馋、不重復(fù)、并且按序到達(dá)惨好。
但是在網(wǎng)絡(luò)中相連兩端之間的介質(zhì)椅邓,是復(fù)雜的,并不確保數(shù)據(jù)的可靠性交付昧狮,那么 TCP 是怎么樣解決問題的景馁?
這就需要了解 TCP 的幾種技術(shù):
滑動窗口
超時重傳
流量控制
擁塞控制
下面來分別講一下這幾種技術(shù)的實(shí)現(xiàn)原理。
超時重傳
重傳時機(jī)
TCP 報(bào)文段在傳輸?shù)倪^程中逗鸣,下面的情況都是有可能發(fā)生的:
數(shù)據(jù)包中途丟失合住;
數(shù)據(jù)包順利到達(dá)绰精,但對方發(fā)送的 ACK 報(bào)文中途丟失;
數(shù)據(jù)包順利到達(dá)透葛,但對方異常未響應(yīng) ACK 或被對方丟棄笨使;
當(dāng)出現(xiàn)這些異常情況時,TCP 就會超時重傳僚害。
TCP 每發(fā)送一個報(bào)文段硫椰,就對這個報(bào)文段設(shè)置一次計(jì)時器。只要計(jì)時器設(shè)置的重傳時間到了萨蚕,但還沒有收到確認(rèn)靶草,就重傳這一報(bào)文段,這個就叫做「超時重傳」岳遥。
重傳算法
先認(rèn)識兩個概念
RTO ( Retransmission Time-Out ) 重傳超時時間
指發(fā)送端發(fā)送數(shù)據(jù)后奕翔、重傳數(shù)據(jù)前等待接收方收到該數(shù)據(jù) ACK 報(bào)文的時間。
大白話就是浩蓉,需要等待多長時間還沒收到確認(rèn)派继,就重新傳一次。
RTO 的設(shè)置對于重傳非常重要:
設(shè)長了捻艳,重發(fā)就慢驾窟,沒有效率,性能差认轨;
設(shè)短了绅络,重發(fā)得就快,會增加網(wǎng)絡(luò)擁塞好渠,導(dǎo)致更多的超時昨稼,更多的超時導(dǎo)致更多的重發(fā)节视。
RTT ( Round Trip Time ) 連接往返時間
指發(fā)送端從發(fā)送 TCP 包開始到接收它的 ACK 報(bào)文之間所耗費(fèi)的時間拳锚。
而在實(shí)際的網(wǎng)絡(luò)傳輸中,RTT 的值每次都是隨機(jī)的寻行,無法事先預(yù)預(yù)知霍掺。
TCP 通過測量來獲得連接當(dāng)前 RTT 的一個估計(jì)值,并以該 RTT 估計(jì)值為基準(zhǔn)來設(shè)置當(dāng)前的 RTO拌蜘。
這就引入了一類算法的稱呼:自適應(yīng)重傳算法(Adaptive Restransmission Algorithm)
這類算法的關(guān)鍵就在于對當(dāng)前 RTT 的準(zhǔn)確估計(jì)杆烁,以便適時調(diào)整 RTO。
關(guān)于自適應(yīng)重傳算法简卧,經(jīng)歷過多次的迭代和修正兔魂。
從 1981 年的RFC793提及的經(jīng)典算法,到 1987 年 Karn 提出的 Karn/Partridge 算法举娩,再到后來的 1988 年的 Jacobson / Karels 算法析校。
最后的這個算法在被用在今天的 TCP 協(xié)議中(Linux的源代碼在:tcp_rtt_estimator)构罗。
自適應(yīng)重傳算法的發(fā)展讀者有興趣可以參考其他資料,在這里我拎一個現(xiàn)在在用的算法出來講講智玻,隨意感受一下遂唧。
Jacobson / Karels 算法
1988年,有人推出來了一個新的算法吊奢,這個算法叫 Jacobson / Karels Algorithm(參看RFC6298)盖彭。
其計(jì)算公式:
SRTT = SRTT + α ( RTT – SRTT ) —— 計(jì)算平滑 RTT
DevRTT = ( 1-β )DevRTT + β( | RTT - SRTT | ) ——計(jì)算平滑 RTT 和真實(shí)的差距(加權(quán)移動平均)
RTO= μSRTT + ?DevRTT
其中:
α、β页滚、μ召边、?是可以調(diào)整的參數(shù),在 RFC6298 中給出了對應(yīng)的參考值逻谦,而在Linux下掌实,α = 0.125,β = 0.25邦马, μ = 1贱鼻,? = 4;
SRTT 是 Smoothed RTT 的意思滋将,是 RTT 的平滑計(jì)算值邻悬,即根據(jù)每次測量的 RTT 和舊的 RTT 進(jìn)行運(yùn)算,得出新的 RTT随闽。SRTT 的值父丰,會在每一次測量到 RTT 之后進(jìn)行更新;
DevRTT 是 Deviation RTT 的意思掘宪,根據(jù)每次測量的 RTT 和舊的 SRTT 值進(jìn)行運(yùn)算蛾扇,得出新的 DevRTT;
由算法可以知道 RTO 的值會根據(jù)每次測量的 RTT 值變化而變化魏滚,基本要點(diǎn)是 TCP 監(jiān)視每個連接的性能镀首,由每一個 TCP 的連接情況推算出合適的 RTO 值,根據(jù)不同的網(wǎng)絡(luò)情況鼠次,自動修改 RTO 值更哄,以適應(yīng)負(fù)責(zé)的網(wǎng)絡(luò)變化。
擁塞控制
慢啟動(Slow Start) 與 擁塞避免(Congestion Avoidance)
[ ] TODO
快速重傳(Fast Retransmit) 與 快速恢復(fù)(Fast Recovery)
[ ] TODO
滑動窗口 Sliding Window
滑動窗口協(xié)議比較復(fù)雜腥寇,也是 TCP 協(xié)議的精髓所在成翩。
TCP 頭里有一個字段叫 Window,叫 Advertised-Window赦役,這個字段是接收端告訴發(fā)送端自己還有多少緩沖區(qū)可以接收數(shù)據(jù)麻敌。于是發(fā)送端就可以根據(jù)這個接收端的處理能力來發(fā)送數(shù)據(jù),而不會導(dǎo)致接收端處理不過來掂摔。
滑動窗口分為「接收窗口」和「發(fā)送窗口」
因?yàn)?TCP 協(xié)議是全雙工的术羔,會話的雙方都可以同時接收和發(fā)送职辅,那么就需要各自維護(hù)一個「發(fā)送窗口」和「接收窗口」。
發(fā)送窗口
大小取決于對端通告的接受窗口聂示。
只有收到對端對于本端發(fā)送窗口內(nèi)字節(jié)的 ACK 確認(rèn)域携,才會移動發(fā)送窗口的左邊界。
下圖是發(fā)送窗口的示意圖:
對于發(fā)送窗口鱼喉,在緩存內(nèi)的數(shù)據(jù)有四種狀態(tài):
#1 已發(fā)送秀鞭,并得到接收方 ACK 確認(rèn);
#2 已發(fā)送扛禽,但還未收到接收方 ACK锋边;
#3 未發(fā)送,但接收方允許發(fā)送编曼,接收方還有空間
#4 未發(fā)送豆巨,且接收方不允許發(fā)送,接收方?jīng)]有空間
如果下一刻掐场,收到了接收方對于 32-36 字節(jié)序的數(shù)據(jù)包的 ACK 確認(rèn)往扔,那么發(fā)送方的窗口就會發(fā)生「滑動」。
并且發(fā)送下一個 46-51 字節(jié)序的數(shù)據(jù)包熊户。
滑動窗口的概念萍膛,描述了 TCP 的數(shù)據(jù)是怎么發(fā)送,以及怎么接收的嚷堡。
TCP 的滑動窗口是動態(tài)的障陶,我們可以想象成小學(xué)常見的一個數(shù)學(xué)題棺滞,一個水池崔挖,體積 V碱工,每小時進(jìn)水量 V1, 出水量 V2。
當(dāng)水池滿了就不允許再注入了北苟,如果有個液壓系統(tǒng)控制水池大小桩匪,那么就可以控制水的注入速率和量了。
應(yīng)用程序可以根據(jù)自身的處理能力變化粹淋,通過 API 來控制本端 TCP 接收窗口的大小吸祟,來進(jìn)行流量控制瑟慈。
接收窗口
大小取決于應(yīng)用桃移、系統(tǒng)、硬件的限制葛碧。
下圖是接收窗口的示意圖(找不到圖借杰,唯有自己畫了):
相對于發(fā)送窗口,接受窗口在緩存內(nèi)的數(shù)據(jù)只有三種狀態(tài):
已接收已確認(rèn)进泼;
未接收蔗衡,準(zhǔn)備接收纤虽;
未接收,并未準(zhǔn)備接收绞惦;
下一刻接收到來自發(fā)送端的 32-36 數(shù)據(jù)包逼纸,然后回送 ACK 確認(rèn)報(bào),并且移動接收窗口济蝉。
另外接收端相對于發(fā)送端還有不同的一點(diǎn)杰刽,只有前面所有的段都確認(rèn)的情況下才會移動左邊界,
在前面還有字節(jié)未接收但收到后面字節(jié)的情況下王滤,窗口不會移動贺嫂,并不對后續(xù)字節(jié)確認(rèn),以此確保對端會對這些數(shù)據(jù)重傳雁乡。
假如 32-36 字節(jié)不是一個報(bào)文段的第喳,而是每個字節(jié)一個報(bào)文段的話,那么就會分成了 5 個報(bào)文段踱稍。
在實(shí)際的網(wǎng)絡(luò)環(huán)境中曲饱,不能確保是按序收到的,其中會有一些早達(dá)到珠月,一些遲到達(dá)渔工。
如圖中的 34、35 字節(jié)序桥温,先收到了引矩,接收窗口也不會移動。
因?yàn)橛锌赡?32侵浸、33 字節(jié)序會出現(xiàn)丟包或者超時旺韭,這時就需要發(fā)送端重發(fā)報(bào)文段了。