一個(gè)網(wǎng)管的自我修養(yǎng)之TCP協(xié)議
今天,繼續(xù)來(lái)網(wǎng)管的自我修養(yǎng)之TCP協(xié)議,這可是除 IP 協(xié)議外另一個(gè)核心協(xié)議了。
TCP 協(xié)議是網(wǎng)絡(luò)傳輸中至關(guān)重要的一個(gè)協(xié)議,它位于傳輸層。向上支持 FTP、TELNET、SMTP、DNS、HTTP等常見的應(yīng)用層協(xié)議,向下要與網(wǎng)絡(luò)層的 IP 協(xié)議相互配合,實(shí)現(xiàn)可靠的網(wǎng)絡(luò)傳輸。
分層網(wǎng)絡(luò)模型
OSI 7層模型
為了讓全世界的計(jì)算機(jī)有效的互聯(lián)起來(lái),國(guó)際標(biāo)準(zhǔn)化組織提出了一種概念化的網(wǎng)絡(luò)模型,開放式系統(tǒng)互聯(lián)模型(Open System Interconnection Model),簡(jiǎn)稱 OSI 模型。
自上而下依次為應(yīng)用層、表示層、會(huì)話層、傳輸層、網(wǎng)絡(luò)層、數(shù)據(jù)鏈路層、物理層。
應(yīng)用層
應(yīng)用層提供為應(yīng)用軟件而設(shè)計(jì)的接口,以設(shè)置與另一應(yīng)用軟件之間的通信。例如:HTTP、HTTPS、FTP、Telnet、SSH、SMTP、POP3等。
表示層
表示層把數(shù)據(jù)轉(zhuǎn)換為能與接收者的系統(tǒng)格式兼容并適合傳輸?shù)母袷健?/p>
會(huì)話層
會(huì)話層負(fù)責(zé)在數(shù)據(jù)傳輸中設(shè)置和維護(hù)計(jì)算機(jī)網(wǎng)絡(luò)中兩臺(tái)計(jì)算機(jī)之間的通信連接。
傳輸層
傳輸層把傳輸表頭(TH)加至數(shù)據(jù)以形成數(shù)據(jù)包。傳輸表頭包含了所使用的協(xié)議等發(fā)送信息。例如:傳輸控制協(xié)議(TCP)等。
網(wǎng)絡(luò)層
網(wǎng)絡(luò)層決定數(shù)據(jù)的路徑選擇和轉(zhuǎn)寄,將網(wǎng)絡(luò)表頭(NH)加至數(shù)據(jù)包,以形成分組。網(wǎng)絡(luò)表頭包含了網(wǎng)絡(luò)資料。例如:互聯(lián)網(wǎng)協(xié)議(IP)等。
數(shù)據(jù)鏈路層
數(shù)據(jù)鏈路層負(fù)責(zé)網(wǎng)絡(luò)尋址、錯(cuò)誤偵測(cè)和改錯(cuò)。當(dāng)表頭和表尾被加至數(shù)據(jù)包時(shí),會(huì)形成信息框。數(shù)據(jù)鏈表頭(DLH)是包含了物理地址和錯(cuò)誤偵測(cè)及改錯(cuò)的方法。數(shù)據(jù)鏈表尾(DLT)是一串指示數(shù)據(jù)包末端的字符串。例如以太網(wǎng)、無(wú)線局域網(wǎng)(Wi-Fi)和通用分組無(wú)線服務(wù)(GPRS)等。
分為兩個(gè)子層:邏輯鏈路控制(logical link control,LLC)子層和介質(zhì)訪問控制(Media access control,MAC)子層。
物理層
物理層在局部局域網(wǎng)上傳送數(shù)據(jù)幀,它負(fù)責(zé)管理電腦通信設(shè)備和網(wǎng)絡(luò)媒體之間的互通。包括了針腳、電壓、線纜規(guī)范、集線器、中繼器、網(wǎng)卡、主機(jī)接口卡等。
OSI 模型是國(guó)際標(biāo)準(zhǔn)模型,是指導(dǎo)互聯(lián)網(wǎng)模型的概念標(biāo)準(zhǔn)。而在實(shí)際的設(shè)計(jì)實(shí)現(xiàn)過(guò)程中,最后形成了 TCP/IP 4層模型結(jié)構(gòu)。
TCP/IP 4層模型
TCP/IP 模型實(shí)際上并不單單指 TCP 和 IP,實(shí)際上這一個(gè)協(xié)議簇,還包含了其他的一些協(xié)議,比如 UDP、ICMP、IGMP 等。
TCP/IP 模型是事實(shí)上的標(biāo)準(zhǔn)模型,在 7 層模型的基礎(chǔ)上將最上面三層的應(yīng)用層、表示層、會(huì)話層統(tǒng)一為應(yīng)用層,將數(shù)據(jù)鏈路層和物理層統(tǒng)一為鏈路層或者叫網(wǎng)絡(luò)接口層。
實(shí)際應(yīng)用中還是以 4 層模型為準(zhǔn),畢竟這才是事實(shí)上的標(biāo)準(zhǔn)。還有一種 5 層模型的說(shuō)法,實(shí)際上就是把 7 層中的應(yīng)用層、表示層、會(huì)話層合并為應(yīng)用層,其他層保持不變。
數(shù)據(jù)的加工和傳輸過(guò)程
TCP/IP 模型每個(gè)層都有各自的功能和分工,當(dāng)有用戶數(shù)據(jù)想要發(fā)送給另一臺(tái)設(shè)備的時(shí)候,數(shù)據(jù)自上而下,從應(yīng)用層向鏈路層傳遞有一個(gè)復(fù)雜的過(guò)程。
以 Telnet 為例,Telnet 在傳輸層是使用 TCP 協(xié)議的。
數(shù)據(jù)從應(yīng)用層進(jìn)入,到達(dá)傳輸層,添加上 TCP首部,將數(shù)據(jù)加工成 TCP 段,稱為 Segment。這是為了保證數(shù)據(jù)的可靠性。
接著數(shù)據(jù)到達(dá)網(wǎng)絡(luò)層,在網(wǎng)絡(luò)層使用 IP 協(xié)議,被添加上 IP 首部,將數(shù)據(jù)加工成 IP數(shù)據(jù)報(bào),稱為 datagram 。經(jīng)過(guò)網(wǎng)絡(luò)層 IP 協(xié)議的加工,指定目標(biāo)地址和 MAC 地址,保證數(shù)據(jù)準(zhǔn)確的發(fā)送到目標(biāo)機(jī)器。
接著數(shù)據(jù)到達(dá)鏈路層,添加上以太網(wǎng)頭部,將數(shù)據(jù)加工成以太網(wǎng)幀,稱為 frame,包含了網(wǎng)卡等硬件相關(guān)的數(shù)據(jù)。
無(wú)論是 Telnet 還是 HTTP,都至少涉及到兩臺(tái)設(shè)備才能稱之為網(wǎng)絡(luò)互連,那發(fā)送方有一個(gè)數(shù)據(jù)自應(yīng)用層向底層鏈路層的加工過(guò)程,對(duì)應(yīng)的,在數(shù)據(jù)接收方,有一個(gè)數(shù)據(jù)從鏈路層向應(yīng)用層解析的過(guò)程。這中間可能經(jīng)歷了漫長(zhǎng)的傳輸介質(zhì),比如光纖,還可能有若干個(gè)中間設(shè)備,比如路由器、交換機(jī)等等。要保證數(shù)據(jù)在這么復(fù)雜的網(wǎng)絡(luò)環(huán)境中可靠、準(zhǔn)確的發(fā)送到目標(biāo)機(jī)器,就是靠的 TCP、IP協(xié)議精巧的設(shè)計(jì)。
TCP 協(xié)議
TCP,全稱是 Transmission Control Protocol,傳輸控制協(xié)議。是一種面向連接的、可靠的字節(jié)流服務(wù)協(xié)議,正因?yàn)樗WC可靠性,所以比起 UDP 協(xié)議要復(fù)雜的多,正是由于這種復(fù)雜性,導(dǎo)致它的性能比 UDP 差。
TCP 是 TCP/IP 模型中的傳輸層一個(gè)最核心的協(xié)議,不僅如此,在整個(gè) 4 層模型中,它都是核心的協(xié)議,要不然模型怎么會(huì)叫做 TCP/IP 模型呢。
它向下使用網(wǎng)絡(luò)層的 IP 協(xié)議,向上為 FTP、SMTP、POP3、SSH、Telnet、HTTP 等應(yīng)用層協(xié)議提供支持。其他的還有我們常用的 Redis 的 RESP 協(xié)議、MongoDB的網(wǎng)絡(luò)協(xié)議,以及我們編程中用到的 Socket,都是 TCP 協(xié)議在背后提供支持的。
網(wǎng)絡(luò)協(xié)議是通信計(jì)算機(jī)雙方必須共同遵從的一組約定。如怎么樣建立連接、怎么樣互相識(shí)別等。只有遵守這個(gè)約定,計(jì)算機(jī)之間才能相互通信交流。它的三要素是:語(yǔ)法、語(yǔ)義、時(shí)序。
- 語(yǔ)法:即數(shù)據(jù)與控制信息的結(jié)構(gòu)或格式;
- 語(yǔ)義:即需要發(fā)出何種控制信息,完成何種動(dòng)作以及做出何種響應(yīng);
- 時(shí)序(同步):即事件實(shí)現(xiàn)順序的詳細(xì)說(shuō)明。
TCP 協(xié)議格式
TCP首部 + 用戶數(shù)據(jù)被稱為TCP段,其中 TCP 首部就是這里要主要研究的 TCP 協(xié)議的核心所在,用戶數(shù)據(jù)部分是 TCP 段的負(fù)載。
TCP 段的大小也是有限制的,最大是 1460 字節(jié),這是怎么算出的呢?
最終由網(wǎng)卡發(fā)出去的數(shù)據(jù)包叫做以太網(wǎng)幀,以太網(wǎng)幀由以太網(wǎng)首部和負(fù)載構(gòu)成。
以太網(wǎng)幀的負(fù)載就是一個(gè) IP 數(shù)據(jù)報(bào),IP數(shù)據(jù)報(bào)由IP首部和負(fù)載構(gòu)成。
IP數(shù)據(jù)報(bào)的負(fù)載就是一個(gè) TCP段。所以,TCP段所能搭載的最大數(shù)據(jù)量可以這樣計(jì)算出來(lái):
段搭載的數(shù)據(jù)大小以太網(wǎng)幀大小以太網(wǎng)首部首部首部
以太網(wǎng)幀的大小是固定的 1522字節(jié),而IP首部和TCP首部的大小是不固定的,但是最少會(huì)各占20字節(jié),所以最后算下來(lái) TCP段搭載的數(shù)據(jù)大小最多為 1460字節(jié)。
TCP段搭載的數(shù)據(jù)大?。ㄗ疃?460) = 以太網(wǎng)幀大?。?522字節(jié))-以太網(wǎng)首部(22字節(jié))-IP首部(最少20字節(jié))-TCP首部(最少20字節(jié))
下圖是TCP協(xié)議的示意圖,如果不算「可選項(xiàng)」部分的話,共占用 32bit x 5 = 160bit,也就是20個(gè)字節(jié)。
源端口和目標(biāo)端口
源端口和目標(biāo)端口分別占用 2個(gè)字節(jié),共占用 4 字節(jié),分別記錄數(shù)據(jù)發(fā)送端的端口號(hào)和數(shù)據(jù)接收端的端口號(hào),這兩個(gè)標(biāo)記和 IP 協(xié)議中記錄的發(fā)送端 IP 和接收端 IP組合起來(lái),便可確定一個(gè)唯一的 TCP 連接。
序號(hào)
由于TCP段的大小有限制,當(dāng)要傳輸?shù)臄?shù)據(jù)量大于這個(gè)限制的時(shí)候,就要對(duì)數(shù)據(jù)進(jìn)行分段,一段一段的發(fā)送,既然發(fā)送方要分段,那接收方就要對(duì)分段進(jìn)行重組,才能還原回原始數(shù)據(jù)。在重組的過(guò)程中,要保證各段間的先后順序,序號(hào)正是起到保證重組順序的作用。
序號(hào)占用 4 字節(jié),32 位,它的范圍是 [0,]。TCP是字節(jié)流服務(wù),會(huì)對(duì)每一個(gè)發(fā)送的字節(jié)進(jìn)行編號(hào)。在建立連接的時(shí)候,系統(tǒng)會(huì)給定一個(gè) ISN(初始序號(hào)),然后這個(gè)設(shè)備在當(dāng)前連接中發(fā)送的第一個(gè)字節(jié)的序號(hào)就是 ISN+1,假設(shè) ISN 初始為0,那第一個(gè)字節(jié)的序號(hào)就是 1。
舉個(gè)例子,假設(shè)ISN為0,發(fā)送端第一次發(fā)送 100 字節(jié)的數(shù)據(jù)包,那這第一個(gè) TCP段的序號(hào)就是1,下次再發(fā)送 100字節(jié)的數(shù)據(jù)包,那這第二個(gè) TCP段的序號(hào)就是 101。
這樣一來(lái),最大可以一直標(biāo)記 個(gè)字節(jié),也就是 4個(gè)G的數(shù)據(jù)。當(dāng)達(dá)到最大值后,又會(huì)從 0 開始標(biāo)記。
序號(hào)只有在下面兩種情況下才有用:
- 數(shù)據(jù)字段至少包含一個(gè)字節(jié)。
- 是一個(gè) SYN 段,或者是 FIN 段,或者是 RST 段。
確認(rèn)序號(hào)
當(dāng)數(shù)據(jù)發(fā)送出去,接收方收到之后,會(huì)回復(fù)一個(gè)確認(rèn)序號(hào)回復(fù)給發(fā)送方,這個(gè)確認(rèn)序號(hào)表示接收方希望下次接收的序號(hào)。例如發(fā)送了序號(hào)為501的,長(zhǎng)度為100的TCP段,那接收方收到后要回復(fù) 601的確認(rèn)序號(hào),表示【0-600】的字節(jié)已經(jīng)接收,下次希望收到第 601個(gè)字節(jié)以后的數(shù)據(jù)。
為了提高效率,并不是每次接收到TCP段都會(huì)馬上回復(fù)給發(fā)送方,而是采用累積確認(rèn)的方式,即每傳送多個(gè)連續(xù) TCP 段,可以只對(duì)最后一個(gè) TCP 段進(jìn)行確認(rèn)。
確認(rèn)序號(hào)只有在 ACK 標(biāo)志位被設(shè)置的時(shí)候才有效。
首部長(zhǎng)度
之所以需要首部長(zhǎng)度,是因?yàn)榭蛇x項(xiàng)的大小是不固定的,如果沒有可選項(xiàng)的話,那首部長(zhǎng)度就是 20字節(jié)。這個(gè)標(biāo)示部分占 4 bit,單位是4字節(jié),4bit 可表示的最大值是 15,一個(gè)單位表示的長(zhǎng)度是4字節(jié),所以首部長(zhǎng)度最大可以是 15 x 4字節(jié),也就是 60 字節(jié)。
保留
顧名思義,是保留位,占用6個(gè)比特位,目前的值為 0。
6個(gè)標(biāo)志位
協(xié)議中有 6 個(gè)比特標(biāo)記位,可以理解為 TCP 段的類型。
URG
1個(gè)比特位,當(dāng)被設(shè)置為1時(shí),表明緊急指針字段有效,該報(bào)文段有緊急數(shù)據(jù),應(yīng)盡快發(fā)送。
ACK
當(dāng) ACK 設(shè)置為1時(shí),確認(rèn)號(hào)才有效,連接建立后,所有的報(bào)文段ACK都為 1。
PSH
當(dāng) PSH 設(shè)置為1時(shí),接收方應(yīng)該盡快將這個(gè)報(bào)文段交給應(yīng)用層,而不再等待整個(gè)緩存填滿再交付。
RST
當(dāng) RST 為1時(shí),表示連接出現(xiàn)嚴(yán)重錯(cuò)誤,必須重新建立連接。
SYN
在建立連接時(shí)用到。
當(dāng)SYN=1,ACK=0時(shí),表明這是一個(gè)連接請(qǐng)求報(bào)文段。
當(dāng)SYN=1,ACK=1時(shí),表明對(duì)方同意連接。
FIN
用來(lái)釋放一個(gè)連接窗口。當(dāng)FIN=1時(shí),表明此報(bào)文段的發(fā)送方不再發(fā)送數(shù)據(jù),請(qǐng)求釋放單向連接。TCP斷開連接用到。
窗口大小
大小為2個(gè)字節(jié),表示發(fā)送方自己的接收窗口,用來(lái)告訴對(duì)方允許發(fā)送的數(shù)據(jù)量,最大為65535字節(jié)。
檢驗(yàn)和
校驗(yàn)和是必需的,是一個(gè)端到端的校驗(yàn)和,由發(fā)送端計(jì)算,然后由接收端驗(yàn)證。其目的是為了發(fā)現(xiàn)TCP首部和數(shù)據(jù)在發(fā)送端到接收端之間發(fā)生的任何改動(dòng)。如果接收方檢測(cè)到校驗(yàn)和有差錯(cuò),則TCP段會(huì)被直接丟棄。
緊急指針
占2字節(jié),當(dāng)URG=1時(shí),緊急指針表示本報(bào)文段中的緊急數(shù)據(jù)的字節(jié)數(shù),表示從這個(gè) TCP段的序號(hào)開始的后的若干個(gè)字節(jié)是緊急數(shù)據(jù),之后的就是普通數(shù)據(jù)。
假設(shè)此TCP段的序號(hào)為101,緊急指針為30,那就表示從 101開始,直到 131,【101,131】這個(gè)區(qū)間內(nèi)為緊急數(shù)據(jù)。
三次握手和四次揮手
數(shù)據(jù)要完成傳輸,必須要建立連接。由于建立TCP連接的過(guò)程需要來(lái)回3次,所以,將這個(gè)過(guò)程形象的叫做三次握手。
而連接斷開的時(shí)候要經(jīng)過(guò)四次數(shù)據(jù)傳輸,所以也被稱為4次揮手。
啥都別說(shuō)了,先看圖吧。
三次握手,建立連接
結(jié)合上面的圖來(lái)看更清楚。
先說(shuō)三次握手吧,連接是后續(xù)數(shù)據(jù)傳輸?shù)幕A(chǔ)。就像我們打電話一樣,必須保證我和對(duì)方都拿著電話在聽,才能保證我們兩個(gè)說(shuō)的話對(duì)方能夠接收到。
三次握手大概就是這個(gè)意思:
張三想跟李四聊聊天,于是張三撥通了李四的手機(jī)號(hào),李四聽到鈴聲響起,按了接聽按鈕。
張三:Hi,李四,是你嗎?嘮兩塊錢的呀!
李四:Hi,張三,是我,可以嘮。
張三:好,我確定是你了,接下來(lái)我要開始和你嘮了。
看上去多少有點(diǎn)兒死板,但程序上確實(shí)就是這樣的。
1、第一次握手
首先客戶端發(fā)起連接請(qǐng)求,向服務(wù)器發(fā)送 TCP段,段中包含了目標(biāo)端口和本機(jī)端口,設(shè)置 SYN 標(biāo)志位為1,序號(hào)為 x,也就是初始序號(hào) ISN,如果是第一個(gè)連接,很有可能就是 0。當(dāng)然,此時(shí)服務(wù)器對(duì)應(yīng)的端口要處于監(jiān)聽狀態(tài)。此時(shí),客戶端進(jìn)入 SYNC_SENT 狀態(tài),等待服務(wù)器的確認(rèn)。
2、第二次握手
服務(wù)端收到客戶端發(fā)來(lái)的 SYN 段,對(duì)這個(gè)SYN報(bào)文段進(jìn)行確認(rèn),設(shè)置Acknowledgment Number為x+1(Sequence Number+1),這就是確認(rèn)序號(hào)。同時(shí),服務(wù)端還要發(fā)送 SYN 請(qǐng)求信息,將SYN位置為1,Sequence Number為 y(服務(wù)端的TCP段序號(hào))。服務(wù)器端將上述所有信息放到一個(gè)TCP段(即SYN+ACK段)中,一并發(fā)送給客戶端,此時(shí)服務(wù)器進(jìn)入SYN_RECV狀態(tài)。
3、第三次握手
客戶端接收到服務(wù)端發(fā)來(lái)的 SYN+ACK 段后,發(fā)送一個(gè) ACK 給服務(wù)端,將 Acknowledgment Number 設(shè)置為 y+1。此時(shí)客戶端進(jìn)入 ESTABLISHED(已連接)狀態(tài),服務(wù)端接收到此 TCP段,也將進(jìn)入 ESTABLISHED 狀態(tài),也就標(biāo)志著三次握手結(jié)束,連接成功建立。
三次握手完成之后,連接就建立了,之后就可以愉快的傳輸數(shù)據(jù)了。
四次揮手,江湖再見
一旦有了感情(連接),再分手就難了,難到需要四次揮手。不像 UDP 那樣,沒有連接,說(shuō)分就分。
當(dāng)客戶端和服務(wù)端雙方發(fā)送數(shù)據(jù)完成后,一般會(huì)由客戶端主動(dòng)發(fā)起斷開連接的請(qǐng)求,當(dāng)然,也有少數(shù)情況是服務(wù)端主動(dòng)發(fā)起。
以最常見的客戶端發(fā)起斷開連接為例,說(shuō)一下四次揮手的過(guò)程。
1、第一次揮手
客戶端設(shè)置序號(hào)(Sequence Number)和確認(rèn)序號(hào)(Acknowledgment Number),發(fā)送一個(gè) FIN 段給服務(wù)器。這時(shí),客戶端進(jìn)入 FIN_WAIT_1狀態(tài),意味著客戶端沒有數(shù)據(jù)要發(fā)送了。
2、第二次揮手
服務(wù)端收到 FIN 報(bào)文段,向客戶端發(fā)送一個(gè) ACK 段,客戶端進(jìn)入 FIN_WAIT_2 狀態(tài)。表示服務(wù)端已同意連接關(guān)閉請(qǐng)求。
3、第三次揮手
服務(wù)端向客戶端發(fā)送 FIN 段,請(qǐng)求關(guān)閉連接,同時(shí)服務(wù)端進(jìn)入 LAST_ACK 狀態(tài)。
4、第四次揮手
客戶端收到服務(wù)端發(fā)來(lái)的 FIN 段,向服務(wù)端發(fā)送 ACK 段,之后客戶端進(jìn)入TIME_WAIT狀態(tài)。服務(wù)端收到客戶端的ACK 段以后,就關(guān)閉連接。
上面就是由客戶端主動(dòng)發(fā)起關(guān)閉連接的過(guò)程。
半關(guān)閉狀態(tài)
TCP 是一個(gè)全雙工的字節(jié)流服務(wù),意思就是說(shuō)兩個(gè)端點(diǎn)都可以同時(shí)發(fā)送和接收消息。
正常情況下需要四次揮手才能完成連接的完全斷開。但是有一種情況是這樣的,只主動(dòng)關(guān)閉自己到對(duì)方的連接,但是對(duì)方還是可以給自己發(fā)送數(shù)據(jù)。
用 WireShark 抓住 TCP
Wireshark 是幫助我們分析網(wǎng)絡(luò)請(qǐng)求的利器,建議每個(gè)同學(xué)都裝一個(gè)。我們先用 Wireshark 抓取一個(gè)完整的連接建立、發(fā)送數(shù)據(jù)、斷開連接的過(guò)程。
我這兒只簡(jiǎn)單的介紹一下操作流程。
1、首先打開 Wireshark,在歡迎界面會(huì)列出當(dāng)前機(jī)器上的所有網(wǎng)口、虛機(jī)網(wǎng)口等可以抓取的部件。
2、我接下來(lái)要用 Telnet 連接一個(gè)外網(wǎng)服務(wù)器,所以我選擇第一個(gè) WI-FI:en0,這樣 Wireshark 就會(huì)捕獲我連接的 wifi 上的網(wǎng)絡(luò)傳輸。
3、我只想要抓一下最簡(jiǎn)單的 TCP 連接、發(fā)數(shù)據(jù)、斷開的過(guò)程,所以要做一下抓取過(guò)濾。Wireshark 中的過(guò)濾器可以實(shí)現(xiàn)這樣的需求。在下圖紅框部分可以選了一個(gè)過(guò)濾器。
4、因?yàn)楫?dāng)前沒有直接可用的符合要求的過(guò)濾器,所以,需要自己寫一個(gè)。點(diǎn)擊前面的綠色書簽圖標(biāo),然后在彈出窗口中點(diǎn)擊加號(hào)添加一個(gè)。
內(nèi)容如下,語(yǔ)法就不解釋了,一看就知道。
5、選擇好剛添加的這個(gè)過(guò)濾器,雙擊wifi這個(gè) interface 進(jìn)入就開始捕獲了。
6、我用 telnet 連接這臺(tái)服務(wù)器的 6379 端口 telnet ip 6379,因?yàn)檫@臺(tái)服務(wù)器上裝著 redis,可以模擬發(fā)數(shù)據(jù)。
在控制臺(tái)中連接到 6379 端口成功,然后在 Wireshark 上馬上捕獲到了。
這就是三次握手的過(guò)程。
7、然后直接關(guān)掉終端,這樣會(huì)自動(dòng)觸發(fā)斷開連接,并且發(fā)送最少的數(shù)據(jù),方便我們觀察。整個(gè)的過(guò)程都被 Wireshark 完整的捕捉到了。
第一部分是連接建立的三次握手,第二部分是發(fā)了長(zhǎng)度為 1個(gè)字節(jié)的數(shù)據(jù),第三步是客戶端主動(dòng)發(fā)起的斷開連接的四次揮手過(guò)程。
Wireshark 簡(jiǎn)單介紹
有圖先看圖
概覽信息
也就是圖中最上面的紅色框部分。這一次的連接建立和中斷一共產(chǎn)生了來(lái)回 8 次的請(qǐng)求,每次請(qǐng)求會(huì)在列表上列出時(shí)間、源端IP、目的端IP、以太網(wǎng)幀長(zhǎng)度以及概覽信息,包括數(shù)據(jù)傳輸方向(源端口->目標(biāo)端口)、標(biāo)記情況、序號(hào)、確認(rèn)序號(hào)、窗口大小等等。
以太網(wǎng)幀
在每次請(qǐng)求信息中,還包括以太網(wǎng)幀,因?yàn)樾畔⒆罱K都會(huì)通過(guò)幀的形式發(fā)送出去。
IP數(shù)據(jù)報(bào)
還有 IP 數(shù)據(jù)報(bào)內(nèi)容,其中包含了源端 IP 和 目的端 IP 等信息。
TCP段
TCP 段當(dāng)然是重點(diǎn)了,其中包含了 TCP 協(xié)議中的所有信息,包括端口號(hào)、
粘包、半包
MTU是什么
MTU 全稱是最大傳輸單元,一個(gè)在網(wǎng)絡(luò)上傳輸?shù)陌荒軣o(wú)限大,MTU 一般是對(duì)于鏈路層而言的,拿以太網(wǎng)來(lái)說(shuō),在鏈路層允許發(fā)送的最大的以太網(wǎng)幀的數(shù)據(jù)部分就是 1500字節(jié)。注意是以太網(wǎng)幀的數(shù)據(jù)部分,再加上以太網(wǎng)幀的頭部,會(huì)大于1500字節(jié)。
通過(guò) ifconfig(windows 系統(tǒng)是 ipconfig)可查看本機(jī)各個(gè)網(wǎng)絡(luò)接口(網(wǎng)卡)的MTU 大小。
MSS是什么
MSS 指TCP最大報(bào)文長(zhǎng)度,是TCP協(xié)議定義的一個(gè)選項(xiàng),MSS選項(xiàng)用于在TCP連接建立時(shí),收發(fā)雙方協(xié)商通信時(shí)每一個(gè)報(bào)文段所能承載的最大數(shù)據(jù)長(zhǎng)度。還是用以太網(wǎng)為例,MTU是 1500字節(jié),減去TCP頭(20字節(jié))和IP頭(20字節(jié)),就是MSS 1460字節(jié)。
粘包
粘包就是將幾個(gè)比較小的 TCP 包合并成一個(gè)包,這樣就只發(fā)送一次就可以將多個(gè)小包發(fā)送出去。例如下面這樣,一個(gè)TCP報(bào)文請(qǐng)求中,包含小包A、B、C,每一個(gè)小包原本都是一個(gè)TCP報(bào)文。
為什么要粘包呢?一個(gè)一個(gè)發(fā)送不行嗎?
其實(shí)是可以的,只不過(guò)在多數(shù)情況下來(lái)一個(gè)包馬上就發(fā)送可能會(huì)造成網(wǎng)絡(luò)擁塞,一個(gè)TCP 報(bào)文傳輸?shù)芥溌穼拥臅r(shí)候,會(huì)加上TCP頭和IP頭,占用40字節(jié),如果發(fā)送的數(shù)據(jù)內(nèi)容很小,比如只有1個(gè)字節(jié),為了這一字節(jié)的內(nèi)容,要有40倍的額外的信息被傳輸,是不是有點(diǎn)浪費(fèi)。
為了減少這種浪費(fèi),TCP 協(xié)議就做了一些優(yōu)化,比如 Nagle 算法:
- Nagle 算法規(guī)定每次只有收到上一個(gè)包的確認(rèn)(ACK)之后,才會(huì)發(fā)送下一個(gè)包,在這個(gè)時(shí)間段內(nèi)正好將小的包粘到一起;
- 但是太多的包也不行,大小不能超過(guò) MSS ,也就是前面剛說(shuō)的 1460字節(jié),太大了裝不下;
- 如果沒有那么多小包,也不能一直等著,有一個(gè)超時(shí)時(shí)間,大約是200ms,超過(guò)這個(gè)時(shí)間也要發(fā)送;
由于現(xiàn)在的寬帶和設(shè)備性能的提升,Nagle 算法其實(shí)可以關(guān)閉了,有些設(shè)備上默認(rèn)就是關(guān)閉的,也可以在寫 Socket 的代碼的時(shí)候主動(dòng)關(guān)閉掉,關(guān)閉之后呢,只要接收端處理能力夠快,可以保證來(lái)一個(gè)包馬上發(fā)送,對(duì)那些要求實(shí)時(shí)反饋的應(yīng)用來(lái)說(shuō)尤其重要。
那來(lái)一個(gè)包發(fā)一個(gè)包,是不是就不會(huì)有粘包的問題了?也不是,這就要看接收端的處理能力了,接收端會(huì)有一個(gè)接收緩沖區(qū),來(lái)不及被應(yīng)用程序處理的會(huì)暫時(shí)放到這里,如果應(yīng)用程序處理能力較差,這里還是會(huì)出現(xiàn)粘包。
拆包
既然發(fā)生了粘包,就要把這些大包拆成小包。怎么拆分其實(shí)都是上層應(yīng)用的事兒了,核心要點(diǎn)就是約定好分隔符。舉個(gè)簡(jiǎn)單的例子,比如說(shuō)將包A和包B用一個(gè)特殊字符 $分隔開,那應(yīng)用在拆包的時(shí)候就要根據(jù)這個(gè)特殊字符進(jìn)行分隔。當(dāng)然了,真實(shí)情況要比這個(gè)復(fù)雜的多,如果你用過(guò) Netty,就會(huì)發(fā)現(xiàn) Netty 提供了多種處理粘包拆包的方式。
什么是半包
粘包是為了將多個(gè)小包變成一個(gè)大包,而半包是把超大包拆成小包。比如下圖,假設(shè)包B是一個(gè)很大的包,已經(jīng)超過(guò)了MSS 了,單單發(fā)送它自己都發(fā)不過(guò)去了,所以只能將它拆開,一部分一部分的發(fā)送。
半包就沒那么復(fù)雜了,純粹是因?yàn)閱为?dú)的包太大,協(xié)議不支持這么大的包,只能拆開。
這樣一部分一部分的包,到了接收端之后就要將其合并為一個(gè)整體,合并也比較簡(jiǎn)單,就是如果這個(gè)部分包沒有開始或沒有結(jié)束標(biāo)志,就表示它不是完整的,需要給其找到對(duì)應(yīng)的其他部分。
滑動(dòng)窗口
接收方通告的窗口稱為 offered window,意思就是說(shuō)我這邊可以接受的最大字節(jié)數(shù)為這么多。例如下圖中的紅框部分為 offered window, 大小為 6 字節(jié),發(fā)送端最大一次只能發(fā)送 6 個(gè)字節(jié),要不然接收方就沒有能力接收了。
可用窗口 = offered window - 已經(jīng)發(fā)送但未被確認(rèn)的字節(jié)大小,這個(gè)值由發(fā)送方自己計(jì)算。前面說(shuō)了三次握手,發(fā)送方發(fā)出去包,接收方接到后會(huì)反回一個(gè) ACK,發(fā)出去但未收到ACK的數(shù)據(jù)也會(huì)占用窗口,表明接收方正在處理,所以,可用窗口的大小是 offered window 減去未收到 ACK 的大小。
為什么叫滑動(dòng)窗口呢,看上面的圖,把一個(gè)個(gè)字節(jié)想象成排成一排的格子。
首先看時(shí)刻1:紅色格子的部分就是offered window,大小為6字節(jié),后面10、11、12字節(jié)因?yàn)闆]在窗口內(nèi),所以不能發(fā)送。已發(fā)送但未被確認(rèn)的也占用窗口大小,所以最終可用窗口就是 7、8、9這三個(gè)字節(jié)。
再看時(shí)刻2:剛才未被確認(rèn)的 4、5、6字節(jié)收到了 ACK,所以1-6都變成了過(guò)去式,然后窗口覆蓋到了7、8、9、10、11、12 這6個(gè)字節(jié),對(duì)比時(shí)刻1和時(shí)刻2,給我們的感覺就是窗口(紅色格子)向右滑動(dòng)了,這就是所謂的滑動(dòng)窗口了。
還有,窗口兩個(gè)邊沿的相對(duì)運(yùn)動(dòng)增加或減少了窗口的大小。
- 當(dāng)窗口左邊沿向右邊沿靠近為窗口合攏。這種現(xiàn)象發(fā)生在數(shù)據(jù)被發(fā)送和確認(rèn)時(shí),假設(shè)現(xiàn)在接收方處理數(shù)據(jù)的時(shí)間變長(zhǎng)了,來(lái)不及快速處理,那接收方在下次ACK的時(shí)候返回的窗口大小可能就會(huì)變小。
當(dāng)窗口右邊沿向右移動(dòng)時(shí)將允許發(fā)送更多的數(shù)據(jù), 我們稱之為窗口張開。這種現(xiàn)象發(fā) 生在另一端的接收進(jìn)程讀取已經(jīng)確認(rèn)的數(shù)據(jù)并釋放了 TCP的接收緩存時(shí)。
慢啟動(dòng)和擁塞避免算法
在使用 TCP 傳輸?shù)倪^(guò)程中,肯定是希望數(shù)據(jù)傳送的越快越好,但是在實(shí)際使用場(chǎng)景中,由于發(fā)送端和接收端處理數(shù)據(jù)的速度不一致,或者由于中間路由器性能限制、帶寬限制等原因,發(fā)送的速度越快,越有可能導(dǎo)致丟包的情況。比如一下子發(fā)送了10M的數(shù)據(jù)出來(lái),但是中間路由器只能處理 5 M,很可能就會(huì)把一些包丟棄。
因而設(shè)計(jì)了慢啟動(dòng)和擁塞避免算法,這兩個(gè)設(shè)計(jì)都是為了合理的匹配發(fā)端的發(fā)送速度與收端的處理速度。
慢啟動(dòng)
在連接剛建立的時(shí)候,發(fā)送端也不知道應(yīng)該按什么速度發(fā)比較合適,所以就采用了一種漸進(jìn)式的方式,就是慢啟動(dòng)的方式。
前面說(shuō)了 offered window 是接收端的,在發(fā)送端也有一個(gè)窗口,叫做擁塞窗口,記做 cwnd,擁塞窗口初始化為 1 ,表示 1個(gè)報(bào)文段,也就是允許發(fā)送1個(gè)報(bào)文段,之后每當(dāng)每當(dāng)收到接收端返回的 ACK 時(shí),就將 cwnd 的值加1。第一次發(fā)送一個(gè)數(shù)據(jù)報(bào),當(dāng)收到 ACK 后,cwnd 變?yōu)?,然后下一次發(fā)送兩個(gè)數(shù)據(jù)報(bào),當(dāng)收到這兩個(gè)數(shù)據(jù)報(bào)的 ACK 時(shí),cwnd 就變成 4 。以此類推,這個(gè)增長(zhǎng)是呈指數(shù)級(jí)的。
但是,在這個(gè)過(guò)程中,也是有限制的,發(fā)送的數(shù)據(jù)報(bào)大小要在消息接收端返回的通告窗口大小和 cwnd 中取較小的那個(gè)值。假設(shè)一個(gè)報(bào)文大小為 1024 字節(jié),當(dāng) cwnd 為2,通告窗口大小為 4096 字節(jié)時(shí),那發(fā)送端你可以連著發(fā)送2個(gè)數(shù)據(jù)報(bào),也就是取 cwnd 的值,當(dāng) cwnd 為8 時(shí),通告窗口大小仍然為 4096 字節(jié)時(shí),那發(fā)送端最多可連續(xù)發(fā)送 4 個(gè)數(shù)據(jù)報(bào),也就是不能超過(guò) 4096 字節(jié)。
擁塞避免
擁塞避免算法其實(shí)和慢啟動(dòng)是在一起使用的。在慢啟動(dòng)中除了有擁塞窗口外, 還有一個(gè)叫做啟動(dòng)門限(ssthresh)的參數(shù)。啟動(dòng)門限默認(rèn)的是 65535 字節(jié)。
在慢啟動(dòng)中,cwnd 是呈指數(shù)級(jí)增長(zhǎng),但是這個(gè)增長(zhǎng)速度太快了,所以,擁塞避免算法就是讓這個(gè)增速減緩的方式。
當(dāng) cwnd < ssthresh 的時(shí)候,就使用慢啟動(dòng)。
當(dāng) cwnd > ssthresh 的時(shí)候,就啟動(dòng)擁塞避免算法。
擁塞避免算法保證當(dāng) cwnd 超過(guò)限制之后,每次收到一個(gè)確認(rèn)時(shí)將 cwnd 增加 1/cwnd。
當(dāng)擁塞發(fā)生時(shí)(超時(shí)或收到重復(fù)確認(rèn)),ssthresh 被設(shè)置為當(dāng)前窗口大小的一半(cwnd和接收方通告窗口大小的最小值,但最少為 2個(gè)報(bào)文段)。
用一張圖來(lái)說(shuō)明慢啟動(dòng)和擁塞避免算法
假定當(dāng) cwnd 為32個(gè)報(bào)文段時(shí)就會(huì)發(fā)生擁塞。于是設(shè)置 ssthresh 為1 6個(gè)報(bào)文段, 而 cwnd 為1個(gè)報(bào)文段。在時(shí)刻 0發(fā)送了一個(gè)報(bào)文段, 并假定在時(shí)刻 1接收到它的 ACK,此時(shí) cwnd 增加為2。接著發(fā)送了2個(gè)報(bào)文段,并假定在時(shí)刻 2接收到它們的 ACK,于是 cwnd 增加為4 (對(duì)每個(gè) ACK 增加1次)。這種指數(shù)增加算法一直進(jìn)行到在時(shí)刻 3和4之間收到8個(gè)A C K后 cwnd 等 于 ssthresh 時(shí)才停止,從該時(shí)刻起,cwnd 以線性方式增加,在每個(gè)往返時(shí)間內(nèi)最多增加 1個(gè)報(bào) 文段。
正如我們?cè)谶@個(gè)圖中看到的那樣, 術(shù)語(yǔ)“慢啟動(dòng)”并不完全正確。它只是采用了比引起 擁塞更慢些的分組傳輸速率, 但在慢啟動(dòng)期間進(jìn)入網(wǎng)絡(luò)的分組數(shù)增加的速率仍然是在增加的。只有在達(dá)到 ssthresh 擁塞避免算法起作用時(shí),這種增加的速率才會(huì)慢下來(lái)。
重傳機(jī)制
什么情況下要重傳,當(dāng)發(fā)送端認(rèn)為丟包了就要重傳,有兩種情況下發(fā)送端就認(rèn)為丟包了,于是就會(huì)發(fā)起重傳。
超時(shí)重傳
發(fā)送端在一段時(shí)間(超時(shí)時(shí)間)后沒有收到發(fā)送端返回的 ACK ,就認(rèn)為這個(gè)包丟了,這個(gè)超時(shí)時(shí)間并不是固定的。
這里面有兩個(gè)概念,RTT 和 RTO。
- RTT(Round Trip Time):往返時(shí)延,也就是數(shù)據(jù)包從發(fā)出去到收到對(duì)應(yīng) ACK 的時(shí)間。RTT 是針對(duì)連接的,每一個(gè)連接都有各自獨(dú)立的 RTT。
- RTO(Retransmission Time Out):重傳超時(shí),也就是前面說(shuō)的超時(shí)時(shí)間。
快速重傳
接收端回復(fù)的 ACK 會(huì)帶著包的序號(hào),當(dāng)接收端重復(fù)三次收到同一個(gè)序號(hào)的ACK時(shí),就要重傳這個(gè)包;
比如下面圖中畫的這樣:
1、seq=1的包發(fā)過(guò)去,接收端ACK=2,表示期望下次出現(xiàn)的序號(hào)為2,然后發(fā)送端就發(fā)了 seq=2的包,接收端接到后回復(fù) ACK=3,表示期望下次收到序號(hào)為3的包,這是發(fā)送端第一次收到 ACK=3;
2、發(fā)送端繼續(xù)發(fā)送 seq=3 的包,但是這個(gè)包可能傳輸?shù)谋容^慢(比如路由選擇的不好),接收端一直沒收到;
3、發(fā)送端先不管,繼續(xù)發(fā)送 seq=4 的包,接收端收到后,回復(fù)ACK,正常情況下應(yīng)該是 ACK=5,但是序號(hào)為3的包還沒收到,所以再次回復(fù)ACK=3,這是第二次收到ACK=3;
4、發(fā)送端繼續(xù)不管,接著發(fā)送 seq=5的包,接收端收到后,回復(fù)ACK,正常情況下應(yīng)該是 ACK=6,但是序號(hào)為3的包還沒收到,所以再次回復(fù)ACK=3,這是第三次收到ACK=3;
到目前為止,已經(jīng)收到三次 ACK=3了,然后發(fā)送端就重新發(fā)送 seq=3的包,這時(shí)候就當(dāng)做這個(gè)包已經(jīng)丟了。這就是快速重傳。
?我是風(fēng)箏,主業(yè) Java 程序員,有時(shí)也是用 Python 、React 做一些小東西。喜歡并擅長(zhǎng)解決問題、處理 bug。堅(jiān)持原創(chuàng)干貨輸出,關(guān)注我,一起變優(yōu)秀!
本文轉(zhuǎn)載自微信公眾號(hào)「古時(shí)的風(fēng)箏」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系公眾號(hào)。