在計(jì)算機(jī)領(lǐng)域瞳筏,數(shù)據(jù)的本質(zhì)無(wú)非0和1稚瘾,創(chuàng)造0和1的固然偉大,但真正百花齊放的還是基于0和1之上的各種層次之間的組合(數(shù)據(jù)結(jié)構(gòu))所帶給我們?nèi)祟惛鞣N各樣的可能性姚炕。例如TCP協(xié)議摊欠,我們的生活無(wú)不無(wú)時(shí)無(wú)刻的站在TCP協(xié)議這個(gè)“巨人”的肩膀上丢烘,最簡(jiǎn)單的一個(gè)打開(kāi)手機(jī)的動(dòng)作肚医。所以對(duì)TCP的認(rèn)識(shí)和理解马昨,可謂越來(lái)越常識(shí)化蒙秒。
TCP/IP五層協(xié)議
雖然TCP是一種計(jì)算機(jī)網(wǎng)絡(luò)協(xié)議试和,但本質(zhì)還是人與人之間的一種約定,只不過(guò)由計(jì)算機(jī)去執(zhí)行而已蜓堕,把協(xié)議的細(xì)節(jié)與作用解耦妹萨,讓我們?nèi)祟愔恍鑼W⒂诨谒膽?yīng)用呈現(xiàn)之上即可孵睬。協(xié)議即“規(guī)則”石窑,如果我們把光纖“橫斜面”剖析牌芋,我們看到的就是數(shù)據(jù)的本質(zhì)0和1,如下圖所示:
0和1是點(diǎn)對(duì)點(diǎn)之間通信的信息“載體”松逊,我們需要有一各規(guī)則去翻譯這些“載體”躺屁,好比如小白和小黑之間的“敲聲傳話游戲”的約定,他們可以約定“敲一下”代表“是”经宏,“敲兩下”代表“不是”等犀暑。這些“敲聲”跟光纖上的“0”和“1”都是承載著一樣的任務(wù)——信息載體。
從整個(gè)網(wǎng)絡(luò)層次來(lái)看烛恤,TCP/IP協(xié)議體系是網(wǎng)絡(luò)的一個(gè)核心協(xié)議組母怜,有一點(diǎn)需要知道的是TCP/IP協(xié)議體系并非只有TCP協(xié)議和IP協(xié)議,而是包含了物理層缚柏、鏈路層、網(wǎng)絡(luò)層碟贾、運(yùn)輸層币喧、應(yīng)用層,而每一層次又有不同的協(xié)議袱耽,例如運(yùn)輸層協(xié)議除了TCP協(xié)議還有UDP協(xié)議杀餐。當(dāng)然這里我只是為了接下來(lái)學(xué)習(xí)TCP協(xié)議的一個(gè)宏觀認(rèn)識(shí)。從上圖可以看出朱巨,從0和1的基本信息單元到TCP協(xié)議的數(shù)據(jù)結(jié)構(gòu)還要經(jīng)過(guò)鏈路層和網(wǎng)絡(luò)層的層層分解史翘,換句話說(shuō),也就是TCP協(xié)議的數(shù)據(jù)以“段”單元冀续,封裝在鏈路層的IP協(xié)議上琼讽,IP協(xié)議的數(shù)據(jù)是以“數(shù)據(jù)報(bào)”為單元,它同樣封裝在鏈路層的以太網(wǎng)標(biāo)準(zhǔn)協(xié)議里面洪唐。本文的重點(diǎn)在TCP協(xié)議的學(xué)習(xí)钻蹬,了解了TCP的原理,其他協(xié)議的數(shù)據(jù)結(jié)構(gòu)和邏輯大同小異了凭需。
TCP的首部
從“TCP/IP五層協(xié)議體系圖”可以看出问欠,每一個(gè)協(xié)議都會(huì)有個(gè)“頭部”肝匆,TCP也不例外,其實(shí)這個(gè)“頭部”就是該協(xié)議的數(shù)據(jù)結(jié)構(gòu)以及規(guī)則的說(shuō)明顺献,但無(wú)論協(xié)議的玩法如何變化旗国,它還是離不開(kāi)0和1的信息載體。
源端口號(hào):我們都知道IP是跟主機(jī)相關(guān)注整,而每臺(tái)主機(jī)又可以有不同的應(yīng)用進(jìn)程在運(yùn)行粗仓,所以端口更多可以指運(yùn)行在主機(jī)上的應(yīng)用進(jìn)程,所以源端口號(hào)也就是基于TCP協(xié)議傳輸數(shù)據(jù)的“發(fā)送方”设捐。
目的端口:就是等待TCP協(xié)議發(fā)送方數(shù)據(jù)的“接收方”借浊,其實(shí)所謂的端口也就是應(yīng)用進(jìn)程與應(yīng)用進(jìn)程之間通信的監(jiān)聽(tīng)出入口。
序列號(hào):這個(gè)數(shù)字是用來(lái)表示通信雙方“單向”數(shù)據(jù)量流動(dòng)數(shù)量表示萝招,上面所介紹的0和1是最小的數(shù)據(jù)傳輸單元蚂斤,我們稱為“比特(bit)”。而這個(gè)序列號(hào)記錄的是以“字節(jié)”為單位的計(jì)數(shù)器(1字節(jié)=8比特)槐沼。例如A要傳輸給B的512字節(jié)數(shù)據(jù)曙蒸,假設(shè)初始序列號(hào)為1024(注意:每次初始化序號(hào)都會(huì)不一樣,TCP有一個(gè)比較復(fù)雜的初始化算法)岗钩,那么他們傳輸過(guò)程的序列號(hào)為1536纽窟。這個(gè)序列號(hào)會(huì)隨著雙方“交流”而不斷的增加,因?yàn)樾蛄刑?hào)一共32比特兼吓,所以最大值也就是2^32-1臂港,到達(dá)最大值后重新從0開(kāi)始。因?yàn)門CP是一個(gè)可靠的協(xié)議视搏,序列號(hào)的存在是其可靠的關(guān)鍵因素之一审孽。
確認(rèn)序列號(hào):既然每個(gè)傳輸?shù)淖止?jié)都被計(jì)數(shù),確認(rèn)序列號(hào)包含發(fā)送確認(rèn)的一端所期望收到下一個(gè)序號(hào)浑娜。因此佑力,確認(rèn)序列號(hào)應(yīng)當(dāng)是上次已成功接收到數(shù)據(jù)字節(jié)序列號(hào)加1。只有ACK標(biāo)識(shí)(下面會(huì)介紹)為1時(shí)確認(rèn)序列號(hào)才生效筋遭。因?yàn)門CP為應(yīng)用層提供雙工服務(wù)打颤,意味著數(shù)據(jù)能在兩個(gè)方向上獨(dú)立地進(jìn)行傳輸,因此連接的每一端(客戶端和服務(wù)端)必須保持每個(gè)方向上的傳輸序列號(hào)漓滔。例如A傳送給B的序列號(hào)為1024(A維護(hù))编饺,但B傳送給A的有自己的序列號(hào)需要維護(hù)(B維護(hù))。
首部長(zhǎng)度:TCP首部的“選項(xiàng)”不啟用次和,那么TCP的頭部就是20字節(jié)反肋,但因?yàn)榇嬖凇斑x項(xiàng)”的部分,所以頭部可能存在大于20字節(jié)的可能性踏施。因?yàn)椤笆撞块L(zhǎng)度標(biāo)識(shí)”有4位石蔗,所以最大值為2^4-1=15罕邀,而這個(gè)標(biāo)識(shí)維護(hù)頭部的長(zhǎng)度是以32比特為單元,所以頭部最大長(zhǎng)度為15*32比特(4字節(jié))=60字節(jié)养距。
標(biāo)志:每個(gè)標(biāo)志占1比特诉探,它們中的多個(gè)可同時(shí)被設(shè)置為1,每個(gè)標(biāo)志的用法如下:
URG:緊急指針(urgent pointer)有效棍厌;
ACK:確認(rèn)序號(hào)有效肾胯;
PSH:接收方應(yīng)該盡快將這個(gè)報(bào)文段交給應(yīng)用層;
RST:重建連接耘纱;
SYN:同步序號(hào)用來(lái)發(fā)起一個(gè)連接敬肚;
FIN:發(fā)送端完成發(fā)送任務(wù);
窗口大惺觥:TCP的流量控制由連接的每一端通過(guò)聲明的窗口大小來(lái)提供(以字節(jié)為單位)艳馒,窗口大小是一個(gè)16比特字段,因而窗口最大為65535字節(jié)员寇。換個(gè)說(shuō)法弄慰,窗口好比如“緩沖區(qū)”,TCP是一個(gè)雙工單向傳送的通信協(xié)議蝶锋,雙方都需要有自己的窗口(緩沖區(qū))大小相互告知陆爽,如果接收到的應(yīng)用處理速度慢(從緩沖區(qū)消費(fèi)數(shù)據(jù)慢),那么它的窗口很容易就滿了扳缕,發(fā)送方就會(huì)停止發(fā)送慌闭,等到接受方的窗口有“空余”了才繼續(xù)發(fā)送。
檢驗(yàn)和:檢驗(yàn)和(類數(shù)據(jù)簽名)覆蓋了整個(gè)的TCP報(bào)文段:TCP首部和TCP數(shù)據(jù)第献,因?yàn)門CP是一個(gè)可靠的協(xié)議贡必,所以這是強(qiáng)制性的字段,由發(fā)送方計(jì)算和設(shè)置庸毫,并由接收方進(jìn)行驗(yàn)證,這就是可靠性保證的重要手段衫樊。
緊急指針:只有當(dāng)URG標(biāo)志置1時(shí)緊急指針才有效飒赃。緊急指針是一個(gè)正的偏移量,和序號(hào)字段中的值相加表示緊急數(shù)據(jù)最后一個(gè)報(bào)文段科侈。
選項(xiàng):就是TCP頭部的不是“必須”的選項(xiàng)载佳,例如常見(jiàn)的可選字段是“最長(zhǎng)報(bào)文大小”,又稱為MSS(Maximun Segment Size)臀栈,每個(gè)連接方通常都在通信的第一個(gè)報(bào)文段中指明這個(gè)選項(xiàng)蔫慧。
數(shù)據(jù):整個(gè)TCP報(bào)文段是又報(bào)文頭部和報(bào)文數(shù)據(jù)組成的,除去了頭部就是數(shù)據(jù)权薯,但數(shù)據(jù)是可空的姑躲,例如創(chuàng)建連接(SYN)和結(jié)束傳輸(FIN)的TCP報(bào)文都是沒(méi)有數(shù)據(jù)的睡扬。
TCP連接的建立和終止
TCP建立連接需要三次握手,分別如下:
1)黍析、客戶端(請(qǐng)求方)發(fā)送一個(gè)SYN段指明客戶打算連接的服務(wù)器端口卖怜,以及把初始化序號(hào)x附上,這就是大名鼎鼎的SYN報(bào)文段阐枣,在介紹頭部的時(shí)候已經(jīng)提過(guò)马靠,SYN報(bào)文段是沒(méi)有數(shù)據(jù)的,因?yàn)檫B接都沒(méi)正式連接蔼两,發(fā)送數(shù)據(jù)沒(méi)意義甩鳄。但也提到了客戶端會(huì)附上它的最大報(bào)文段,也就是告訴接收方它最大的一個(gè)報(bào)文段能接受多少數(shù)據(jù)额划。
2)妙啃、服務(wù)端(處于監(jiān)聽(tīng)狀態(tài))收到SYN請(qǐng)求后發(fā)回包含服務(wù)端的初始序號(hào)的SYN報(bào)文段作為應(yīng)答(上文提到過(guò)客戶端和服務(wù)端的初始序號(hào)都是各自維護(hù)的)。同時(shí)锁孟,將確認(rèn)序號(hào)設(shè)置為客戶的ISN加1(因?yàn)镾YN將占用一個(gè)序號(hào))彬祖,以對(duì)客戶的SYN報(bào)文段進(jìn)行確認(rèn)。在服務(wù)端想客戶端響應(yīng)SYN的時(shí)候同樣可能會(huì)附上它接收的最大報(bào)文段品抽,但記住储笑,畢竟最大報(bào)文段是可選的,不一定會(huì)存在圆恤,不相互告知的話就會(huì)使用默認(rèn)值突倍。
3)、客戶端必須將確認(rèn)序號(hào)設(shè)置為服務(wù)器的ISN加1一對(duì)服務(wù)器的SYN報(bào)文段進(jìn)行確認(rèn)盆昙。
當(dāng)以上三個(gè)報(bào)文段完成交互后就證明連接已經(jīng)建立羽历,這個(gè)過(guò)程也成為“三次握手”。接下來(lái)客戶端就可以發(fā)送數(shù)據(jù)給服務(wù)端淡喜,服務(wù)端可以響應(yīng)數(shù)據(jù)秕磷。其實(shí)很多時(shí)候,客戶端在第三個(gè)報(bào)文段(也就是第三次握手)的時(shí)候就已經(jīng)附帶數(shù)據(jù)了炼团。因?yàn)樗呀?jīng)不需要等待對(duì)方第四次握手的交互確認(rèn)澎嚣。正常連接的第四個(gè)報(bào)文段也是客戶端發(fā)送數(shù)據(jù)的報(bào)文段,所以既然第三次和第四次都是客戶端瘟芝,為了省了一個(gè)交互易桃,客戶端可以直接從第三個(gè)報(bào)文段(應(yīng)答服務(wù)端ack)附上數(shù)據(jù)。
建立一個(gè)連接需要三次握手锌俱,而終止一個(gè)連接需要經(jīng)過(guò)4次握手晤郑,這是由于TCP的半關(guān)閉(half close)造成的。既然一個(gè)TCP連接是全雙工的(即數(shù)據(jù)在兩個(gè)方向上能同時(shí)傳遞),因此每個(gè)方向必須單獨(dú)地進(jìn)行關(guān)閉造寝。當(dāng)一端收到一個(gè)FIN磕洪,它必須通知應(yīng)用層另一端已經(jīng)終止了那個(gè)方向的數(shù)據(jù)傳送。發(fā)送FIN通常是應(yīng)用層進(jìn)行關(guān)閉的結(jié)果匹舞。比較常見(jiàn)的還是客戶端關(guān)閉褐鸥,但服務(wù)端也可以設(shè)置主動(dòng)關(guān)閉,例如Nginx相關(guān)策略配置赐稽。
TCP終止連接需要四次握手叫榕,分別如下:
1)、首先關(guān)閉的一方(即發(fā)送第一個(gè)FIN)將執(zhí)行主動(dòng)關(guān)閉姊舵,上圖顯示主動(dòng)關(guān)閉的一方是客戶端晰绎。
2)、當(dāng)服務(wù)端收到這個(gè)FIN報(bào)文段時(shí)括丁,它將發(fā)回一個(gè)ACK荞下,確認(rèn)序號(hào)為收到的序號(hào)加1,就像上圖的ack=u+1史飞,因?yàn)镕IN跟SYN一樣也占用一個(gè)序號(hào)尖昏。
3)、服務(wù)端把收到的FIN的消息告訴應(yīng)用程序(傳送一個(gè)文件結(jié)束符)构资,接著這個(gè)應(yīng)用程序就會(huì)關(guān)閉它的連接(以上提過(guò)抽诉,建立和關(guān)閉都是由應(yīng)用主動(dòng)發(fā)起的),導(dǎo)致服務(wù)端的TCP端發(fā)送一個(gè)FIN給客戶端吐绵。需要注意的是迹淌,畢竟TCP是雙工的,客戶端關(guān)閉連接不代表服務(wù)端就可以立刻關(guān)閉己单,如果客戶端發(fā)起關(guān)閉的時(shí)候唉窃,服務(wù)端還沒(méi)有響應(yīng)完數(shù)據(jù)給客戶端,服務(wù)端還是需要把數(shù)據(jù)發(fā)完了再去關(guān)閉的纹笼,而客戶端主動(dòng)發(fā)起了閉關(guān)也不會(huì)立刻罷工纹份,它還是會(huì)進(jìn)入“FIN_WAIT2”狀態(tài)進(jìn)行數(shù)據(jù)接收,直到服務(wù)端發(fā)送完了并最后發(fā)送結(jié)束連接報(bào)文段(FIN)廷痘,才進(jìn)入TIME_WAIT狀態(tài)矮嫉。
4)、客戶端收到服務(wù)端的FIN報(bào)文段時(shí)牍疏,它會(huì)立刻對(duì)此FIN進(jìn)行ACK回復(fù),服務(wù)端收到后就直接進(jìn)入關(guān)閉狀態(tài)(CLOSED)拨齐。
因?yàn)門CP是全雙工的鳞陨,雙方都各種維護(hù)自己?jiǎn)蜗騻魉蛿?shù)據(jù)的連接,所以必然會(huì)存在雙方同時(shí)主動(dòng)關(guān)閉的情況,如下圖所示:
當(dāng)雙方同時(shí)向?qū)Ψ桨l(fā)送FIN執(zhí)行主動(dòng)連接時(shí)厦滤,雙方均從ESTABLISHED狀態(tài)變?yōu)镕IN_WAIT_1狀態(tài)援岩。雙方都收到FIN后,狀態(tài)由FIN_WAIT_1變遷至CLOSING掏导,并發(fā)送最后的ACK享怀。當(dāng)收到ACK時(shí),雙方的狀態(tài)變?yōu)門IME_WAIT趟咆。
TCP的狀態(tài)遷變
通過(guò)以上建立和終止連接可以看到添瓷,無(wú)論客戶端還是服務(wù)端,無(wú)論是連接方還是結(jié)束方都存在許多“狀態(tài)”值纱,每個(gè)狀態(tài)隨著各種條件不斷變化鳞贷,具體狀態(tài)的遷變可以通過(guò)下圖來(lái)進(jìn)行總結(jié)。
2MSL等待狀態(tài)
從上圖遷變狀態(tài)可以看到虐唠,TCP主動(dòng)關(guān)閉的一方都會(huì)進(jìn)入TIME_WAIT狀態(tài)搀愧,也稱為2MSL(最大報(bào)文段生存時(shí)間)等待狀態(tài)。之所以要等待疆偿,是因?yàn)?b>關(guān)閉方要確認(rèn)處于“CLOSE_WAIT”狀態(tài)的被關(guān)閉方收到它最后的ACK報(bào)文咱筛,報(bào)文的在網(wǎng)絡(luò)上單向傳送的最大時(shí)間叫做MSL,那么等待確認(rèn)報(bào)文來(lái)回的時(shí)間就是2MSL杆故,如果被關(guān)閉方在2MSL內(nèi)都沒(méi)有收到ACK迅箩,它會(huì)繼續(xù)發(fā)送FIN報(bào)文,而如果關(guān)閉方在2MSL內(nèi)沒(méi)有收到對(duì)方的報(bào)文就默認(rèn)對(duì)方已經(jīng)收到反番。
報(bào)文在網(wǎng)絡(luò)上的生存時(shí)間并不只有TCP決定的沙热,在網(wǎng)絡(luò)層的IP協(xié)議對(duì)數(shù)據(jù)報(bào)同樣存在著網(wǎng)絡(luò)單向傳送的時(shí)間限制,這個(gè)限制的約定叫TTL(Time To Live)罢缸。TTL的時(shí)間單位并非時(shí)間單位篙贸,而是“跳數(shù)”,數(shù)據(jù)包每經(jīng)過(guò)一個(gè)路由就叫“一跳”枫疆,不同系統(tǒng)對(duì)IP數(shù)據(jù)包的跳數(shù)初始值都不一樣爵川,例如有些Linux默認(rèn)值是255。每經(jīng)過(guò)一個(gè)路由息楔,總生命跳數(shù)就減1寝贡,直到為0都還沒(méi)有到達(dá)目的地就丟棄。255跳到底是多少秒呢值依?其實(shí)這都是一個(gè)不確定數(shù)字圃泡。如果一個(gè)數(shù)據(jù)包經(jīng)過(guò)255個(gè)路由都還沒(méi)到達(dá)目的地,我想目的地可能是“火星”愿险。并TCP是“坐”在IP協(xié)議之上的颇蜡,所以TCP的MSL肯定不能比TTL短,RFC793[Postel 1981c]指出MSL為2分鐘。然而风秤,實(shí)現(xiàn)中的常用值是30秒鳖目,1分鐘或2分鐘。要知道缤弦,0和1在光纖上傳送的速度是“光速(約300000km/s)”领迈,30秒的時(shí)間跑了不知道多少趟地球了,所以正常情況下都會(huì)大于TTL了(除非部分路由十分磨蹭)碍沐。如果做過(guò)一些高并發(fā)系統(tǒng)的同學(xué)狸捅,多少會(huì)遇到一些諸如time_wait過(guò)多的現(xiàn)象,例如WEB服務(wù)器配置主動(dòng)關(guān)閉連接策略或連接有效時(shí)間短而主動(dòng)關(guān)閉抢韭,大量的time_wait會(huì)占用文件描述符薪贫,而很容易導(dǎo)致耗光系統(tǒng)默認(rèn)的1024個(gè)最大文件打開(kāi)數(shù)(fs.file-max)而無(wú)法正常服務(wù)。
報(bào)文段復(fù)位
從以上狀態(tài)圖還可以看出一個(gè)比較特殊的標(biāo)識(shí)刻恭,就是RST(復(fù)位)標(biāo)識(shí)瞧省。無(wú)論何時(shí)一個(gè)報(bào)文段發(fā)往基準(zhǔn)的連接(由目的IP地址和目的端口號(hào)以及源IP地址和源端口號(hào)指明的連接)出現(xiàn)錯(cuò)誤,TCP都會(huì)發(fā)出一個(gè)復(fù)位段報(bào)文鳍贾。說(shuō)白了就是一個(gè)出錯(cuò)提示報(bào)文鞍匾,通常會(huì)有兩種情況會(huì)產(chǎn)生這種報(bào)文。
情況1:到不存在的端口的連接請(qǐng)求骑科。也就是說(shuō)橡淑,如果你去telnet一個(gè)不存在的端口,目的端口沒(méi)有進(jìn)程在聽(tīng)咆爽,對(duì)方主機(jī)TCP將會(huì)產(chǎn)生一個(gè)復(fù)位報(bào)文告訴連接拒絕梁棠。
情況2:異常終止一個(gè)連接。例如斗埂,當(dāng)服務(wù)器在通信過(guò)程突然被重啟了符糊,而在重啟后客戶端還在發(fā)送信息給服務(wù)端,服務(wù)端就不會(huì)認(rèn)為這是一個(gè)正常的連接而不接受這個(gè)報(bào)文段并向客戶端發(fā)送一個(gè)RST復(fù)位報(bào)文呛凶。而客戶端的TCP服務(wù)接收到RST后就會(huì)告訴應(yīng)用而立刻斷開(kāi)連接停止發(fā)送信息男娄。
從復(fù)位報(bào)文的效果可以看得出RST報(bào)文跟FIN報(bào)文同樣可以終止一個(gè)連接,有時(shí)稱為異常釋放漾稀。異常但不代表只有缺點(diǎn)模闲,存在即合理,它還有兩個(gè)明顯的優(yōu)點(diǎn):(1)丟棄任何待發(fā)送數(shù)據(jù)并立刻發(fā)送復(fù)位段報(bào)文崭捍。(2)RST的接收方會(huì)區(qū)分另一端執(zhí)行的是異常關(guān)閉還是正常關(guān)閉尸折。
同時(shí)打開(kāi)和同時(shí)關(guān)閉
有時(shí)候TCP建立連接不一定必須是三次握手,有時(shí)可能會(huì)是4次殷蛇。沒(méi)錯(cuò)翁授,當(dāng)雙發(fā)同時(shí)進(jìn)行請(qǐng)求主動(dòng)打開(kāi)連接的時(shí)候就是4次拣播,如下圖所示。這個(gè)時(shí)候收擦,并沒(méi)有誰(shuí)是客戶端誰(shuí)是服務(wù)端之稱,因?yàn)殡p方都有主動(dòng)發(fā)送數(shù)據(jù)的權(quán)利谍倦。這種情況應(yīng)該很少見(jiàn)塞赂,如果需要模擬還是可以的,把雙方的網(wǎng)速通過(guò)某些手段把它降低昼蛀,那么就有可能演示宴猾。
學(xué)習(xí)總結(jié)
本次總結(jié)更多是對(duì)TCP協(xié)議的一個(gè)基礎(chǔ)了解,包括TCP建立連接的正常三次握手和十分罕見(jiàn)的同步建立連接的4次握手叼旋,以及關(guān)閉連接的正常4次握手和同步關(guān)閉連接導(dǎo)致雙方都進(jìn)入TIME_WAIT狀態(tài)的4次握手仇哆。最后總體學(xué)習(xí)了TCP客戶端以及服務(wù)端各種狀態(tài)遷變的概要圖,十分清晰地對(duì)TCP各種概況的描述夫植,以及為什么會(huì)有TIME_WAIT和2MSL的概念讹剔。