一、TCP協(xié)議格式
- TCP報(bào)頭當(dāng)中各個(gè)字段的含義如下
源/目的端口號(hào):表示數(shù)據(jù)是從哪個(gè)進(jìn)程來(lái),到發(fā)送到對(duì)端主機(jī)上的哪個(gè)進(jìn)程。
32位序號(hào)/32位確認(rèn)序號(hào):分別代表TCP報(bào)文當(dāng)中每個(gè)字節(jié)數(shù)據(jù)的編號(hào)以及對(duì)對(duì)方的確認(rèn),是TCP保證可靠性的重要字段。
4位TCP報(bào)頭長(zhǎng)度:表示該TCP報(bào)頭的長(zhǎng)度,以4字節(jié)為單位。
6位保留字段:TCP報(bào)頭中暫時(shí)未使用的6個(gè)比特位。
16位窗口大小:保證TCP可靠性機(jī)制和效率提升機(jī)制的重要字段。
16位檢驗(yàn)和:由發(fā)送端填充,采用CRC校驗(yàn)。接收端校驗(yàn)不通過(guò),則認(rèn)為接收到的數(shù)據(jù)有問(wèn)題。(檢驗(yàn)和包含TCP首部+TCP數(shù)據(jù)部分)
16位緊急指針:標(biāo)識(shí)緊急數(shù)據(jù)在報(bào)文中的偏移量,需要配合標(biāo)志字段當(dāng)中的URG字段統(tǒng)一使用。
選項(xiàng)字段:TCP報(bào)頭當(dāng)中允許攜帶額外的選項(xiàng)字段,最多40字節(jié)。
- TCP報(bào)頭當(dāng)中的6位標(biāo)志位:
URG:緊急指針是否有效。
ACK:確認(rèn)序號(hào)是否有效。
PSH:提示接收端應(yīng)用程序立刻將TCP接收緩沖區(qū)當(dāng)中的數(shù)據(jù)讀走。
RST:表示要求對(duì)方重新建立連接。我們把攜帶RST標(biāo)識(shí)的報(bào)文稱為復(fù)位報(bào)文段。
SYN:表示請(qǐng)求與對(duì)方建立連接。我們把攜帶SYN標(biāo)識(shí)的報(bào)文稱為同步報(bào)文段。
FIN:通知對(duì)方,本端要關(guān)閉了。我們把攜帶FIN標(biāo)識(shí)的報(bào)文稱為結(jié)束報(bào)文段。
1.1 TCP如何將報(bào)頭與有效載荷進(jìn)行分離?
觀察TCP協(xié)議格式,報(bào)文部分把選項(xiàng)除去一共是20字節(jié)。所以我們可以先取20字節(jié)。而其中的四位頭部長(zhǎng)度就表示的是報(bào)頭的大小,根據(jù)這個(gè)就可以計(jì)算出選項(xiàng)的大小。
讀取完TCP的基本報(bào)頭和選項(xiàng)字段后,剩下的就是有效載荷了。
- 關(guān)于四位頭部長(zhǎng)度
這里有四個(gè)比特位,如果按照正常的計(jì)算取值范圍就是【0 ~ 15】,但明顯不對(duì),因?yàn)閳?bào)頭最少要為20字節(jié)。
所以規(guī)定TCP報(bào)頭當(dāng)中的4位首部長(zhǎng)度描述的基本單位是4字節(jié),這樣取值范圍就是【0 ~ 60】,所以整個(gè)報(bào)頭的大小范圍是【20 ~ 60】,報(bào)頭中選項(xiàng)字段的長(zhǎng)度最多是40字節(jié)。
1.2 有效載荷如何向上交付?
因?yàn)閼?yīng)用層的每個(gè)進(jìn)程都會(huì)綁定一個(gè)端口號(hào):
- 服務(wù)端顯示綁定一個(gè)端口號(hào)
- 客戶端由操作系統(tǒng)自動(dòng)綁定一個(gè)端口號(hào)
上面把報(bào)頭提取了出來(lái),而報(bào)頭里含有目的端口,就可以向上找到對(duì)應(yīng)的協(xié)議了。
補(bǔ)充:內(nèi)核中用哈希的方式維護(hù)了端口號(hào)與進(jìn)程ID之間的映射關(guān)系,因此傳輸層可以通過(guò)端口號(hào)快速找到其對(duì)應(yīng)的進(jìn)程ID,進(jìn)而找到對(duì)應(yīng)的應(yīng)用層進(jìn)程。綁定映射關(guān)系的時(shí)機(jī):bind端口的時(shí)候。
1.3 TCP報(bào)頭的理解
跟上一章講的UDP報(bào)頭一樣,TCP報(bào)頭就是一個(gè)結(jié)構(gòu)化對(duì)象:
【網(wǎng)絡(luò)編程】傳輸層協(xié)議——UDP協(xié)議
也是內(nèi)核創(chuàng)建一塊內(nèi)存,后邊就拷貝有效載荷,前面就強(qiáng)轉(zhuǎn)成結(jié)構(gòu)化數(shù)據(jù)然后填寫(xiě)每個(gè)字段。
1.4 序號(hào)與確認(rèn)序號(hào)
在講需要與確認(rèn)序號(hào)之前先引入網(wǎng)絡(luò)可靠性的概念:
1.4.1 網(wǎng)絡(luò)不可靠問(wèn)題
現(xiàn)在計(jì)算機(jī)基本都是基于馮諾依曼體系結(jié)構(gòu):
上圖的這些設(shè)備雖然都在一臺(tái)機(jī)器上,但它們都是獨(dú)立的硬件設(shè)備,它們之想要進(jìn)行數(shù)據(jù)交互,就必須要進(jìn)行通信。因此這幾個(gè)設(shè)備實(shí)際是用“線”連接起來(lái)的,其中連接內(nèi)存和外設(shè)之間的“線”叫做IO總線,而連接內(nèi)存和CPU之間的“線”叫做系統(tǒng)總線。
而在一臺(tái)機(jī)器內(nèi),這些"線"的長(zhǎng)度很短,所以傳輸數(shù)據(jù)發(fā)生錯(cuò)誤的概率很小,但是如果要通信的兩個(gè)機(jī)器相隔很遠(yuǎn)(網(wǎng)絡(luò)),那么傳輸數(shù)據(jù)出錯(cuò)的概率也會(huì)大大增加。
所以網(wǎng)絡(luò)傳輸?shù)牟豢煽繂?wèn)題本質(zhì)就是距離變長(zhǎng)了。
- 不可靠問(wèn)題場(chǎng)景
丟包、亂序(網(wǎng)絡(luò)阻塞)、校驗(yàn)錯(cuò)誤(比特位翻轉(zhuǎn))、重復(fù)
怎么保證自己說(shuō)的話對(duì)方聽(tīng)到了呢?答案是得到對(duì)方的回復(fù)(應(yīng)答),只有收到了應(yīng)答,才能保證歷史消息被對(duì)方收到了。只有確認(rèn)了應(yīng)答,才算可靠。
而雙方通信一定會(huì)存在最新消息,最新消息一般是無(wú)法保證可靠的。
由上面描述可知沒(méi)有絕對(duì)的可靠性,只有相對(duì)的可靠性。
TCP保證可靠性的機(jī)制之一就是確認(rèn)應(yīng)答機(jī)制。
所以雙方進(jìn)行通信的時(shí)候可能除了正常的數(shù)據(jù)段,還會(huì)包含確認(rèn)數(shù)據(jù)段。
如圖是雙方通信使用串行方式,即只有收到了確認(rèn)應(yīng)答才會(huì)繼續(xù)發(fā)送數(shù)據(jù)。這樣的效率可想而知是非常低的。
實(shí)際工作中不會(huì)這樣,而是一方同時(shí)發(fā)送多條數(shù)據(jù)段,只要保證所有數(shù)據(jù)段都有應(yīng)答即可。
但此時(shí)就會(huì)有一個(gè)問(wèn)題,那就是這么些數(shù)據(jù)段到達(dá)對(duì)面的順序不一定就是發(fā)送的順序。
比方說(shuō)發(fā)送了四個(gè)數(shù)據(jù)段,結(jié)果只收到了三個(gè)確認(rèn)應(yīng)答,那么怎么知道是哪個(gè)數(shù)據(jù)段發(fā)送失敗了呢?
1.4.2 32位序號(hào)
解決上面的問(wèn)題就是TCP報(bào)頭中的32位序號(hào)字段。
TCP將發(fā)送出去的每個(gè)數(shù)據(jù)段都進(jìn)行了編號(hào),這個(gè)編號(hào)叫做序列號(hào)。
這樣就保證了傳遞數(shù)據(jù)段的有序性。
舉個(gè)例子:
假設(shè)要發(fā)送4000字節(jié)的數(shù)據(jù),分四次發(fā)送,就需要發(fā)送四個(gè)TCP報(bào)文,此時(shí)這四個(gè)TCP報(bào)文當(dāng)中的32位序號(hào)填的就是發(fā)送數(shù)據(jù)中首個(gè)字節(jié)的序列號(hào),因此分別填的是1、1001、2001和3001。
當(dāng)主機(jī)B接收到這四個(gè)TCP報(bào)文的時(shí)候,就可以利用這四個(gè)報(bào)頭中的序號(hào)字段進(jìn)行排序。
1.4.2 32位確認(rèn)序號(hào)
TCP報(bào)頭當(dāng)中的32位確認(rèn)序號(hào)是告訴對(duì)端,我當(dāng)前已經(jīng)收到了哪些數(shù)據(jù),你的數(shù)據(jù)下一次應(yīng)該從哪里開(kāi)始發(fā)。
比方說(shuō)客戶端發(fā)送的數(shù)據(jù)段的序號(hào)是1,報(bào)文中含有1000字節(jié)的數(shù)據(jù),如果服務(wù)端收到了,那么就會(huì)把返回給客戶端的響應(yīng)報(bào)頭中的32位確認(rèn)序號(hào)填寫(xiě)成1001,那么這個(gè)1001就有兩層含義:
1?? 告訴主機(jī)A,序列號(hào)在1001之前的字節(jié)數(shù)據(jù)我已經(jīng)收到了。
2?? 告訴主機(jī)A,下次向我發(fā)送數(shù)據(jù)時(shí)應(yīng)該從序列號(hào)為1001的字節(jié)數(shù)據(jù)開(kāi)始進(jìn)行發(fā)送。
通過(guò)序號(hào)和確認(rèn)序號(hào)就可以表示:
接收方已經(jīng)收到ACK序號(hào)(確認(rèn)序號(hào))之前的所有(連續(xù))報(bào)文。
舉個(gè)例子:
發(fā)送的數(shù)據(jù)都是1000字節(jié)大小。
如果序號(hào)1001的數(shù)據(jù)段沒(méi)有傳遞到主機(jī)B,其他的傳遞到了主機(jī)B,那么1001之后的數(shù)據(jù)段的確認(rèn)序號(hào)都只能填寫(xiě)1001。這表明的就是序列號(hào)在1001之前的數(shù)據(jù)段都被收到了。
- 為什么要有兩組序號(hào)?
為什么不能把32位序號(hào)和32位確認(rèn)序號(hào)壓縮為一個(gè)字段,發(fā)送的時(shí)候就填序號(hào),返回的時(shí)候就填確認(rèn)序號(hào)?
如果是一端發(fā)送數(shù)據(jù)一段接收數(shù)據(jù)當(dāng)然可以使用這種方式,但是TCP是全雙工的,雙方可能同時(shí)要給對(duì)方發(fā)送消息。
雙方發(fā)出的報(bào)文當(dāng)中,不僅需要填充32位序號(hào)來(lái)表明自己當(dāng)前發(fā)送數(shù)據(jù)的序號(hào)。還需要填充32位確認(rèn)序號(hào),對(duì)對(duì)方上一次發(fā)送的數(shù)據(jù)進(jìn)行確認(rèn),告訴對(duì)方下一次應(yīng)該從哪一字節(jié)序號(hào)開(kāi)始進(jìn)行發(fā)送。
1.5 窗口大小
首先要知道TCP是有自己的發(fā)送緩沖區(qū)和接收緩沖區(qū)。
當(dāng)上層調(diào)用write/send,實(shí)際上是把數(shù)據(jù)拷貝到發(fā)送緩沖區(qū)。
當(dāng)上層調(diào)用read/recv,實(shí)際上是把數(shù)據(jù)拷貝到接收緩沖區(qū)。
這樣就會(huì)導(dǎo)致兩種種情況:
發(fā)送數(shù)據(jù)過(guò)快,導(dǎo)致接收緩沖區(qū)被打滿,剩下的報(bào)文都會(huì)被丟棄掉。
發(fā)送數(shù)據(jù)過(guò)慢,影響到上層的業(yè)務(wù)處理。
既然如此,TCP就要控制傳輸速度。所以必須要知道對(duì)方緩沖區(qū)的接受能力。也就是接收緩沖區(qū)剩余空間的大小。
- 怎么知道對(duì)方緩沖區(qū)的剩余空間呢?
通過(guò)16位窗口的字段填寫(xiě)發(fā)送方的剩余緩沖區(qū)的大小。也就是當(dāng)前主機(jī)接收數(shù)據(jù)的能力。那么接收方知道了以后就會(huì)調(diào)整發(fā)送速度。
-
窗口大小字段越大,說(shuō)明接收端接收數(shù)據(jù)的能力越強(qiáng),此時(shí)發(fā)送端可以提高發(fā)送數(shù)據(jù)的速度。
-
窗口大小字段越小,說(shuō)明接收端接收數(shù)據(jù)的能力越弱,此時(shí)發(fā)送端可以減小發(fā)送數(shù)據(jù)的速度。
-
如果窗口大小的值為0,說(shuō)明接收端接收緩沖區(qū)已經(jīng)被打滿了,此時(shí)發(fā)送端就不應(yīng)該再發(fā)送數(shù)據(jù)了。
-
補(bǔ)充一點(diǎn)
因?yàn)榇翱谟?6位,所以窗口最大的內(nèi)存位64k。如果數(shù)據(jù)量太大了就可以用選項(xiàng)字段的一些選項(xiàng)把窗口擴(kuò)大。
1.6 六個(gè)標(biāo)志位
- 為什么會(huì)有標(biāo)志位?
TCP的報(bào)文也是有類(lèi)型的,比方說(shuō)正常通信的普通報(bào)文,建立連接時(shí)發(fā)送的報(bào)文,斷開(kāi)連接發(fā)送的報(bào)文。
針對(duì)這些不同類(lèi)型的報(bào)文需要有對(duì)應(yīng)的動(dòng)作,比方說(shuō)如果收到的是正常通信的報(bào)文,就需要把數(shù)據(jù)放到緩沖區(qū)中,如果收到的是建立連接的報(bào)文,就要進(jìn)行三次握手。
六個(gè)標(biāo)志位就是為了區(qū)分不同的類(lèi)型。
- SYN
報(bào)文當(dāng)中的SYN被設(shè)置為1,表明該報(bào)文是一個(gè)連接建立的請(qǐng)求報(bào)文。
只有在連接建立階段,SYN才被設(shè)置,正常通信時(shí)SYN不會(huì)被設(shè)置。
- FIN
報(bào)文當(dāng)中的FIN被設(shè)置為1,表明該報(bào)文是一個(gè)連接斷開(kāi)的請(qǐng)求報(bào)文。
只有在斷開(kāi)連接階段,F(xiàn)IN才被設(shè)置,正常通信時(shí)FIN不會(huì)被設(shè)置。
- ACK
報(bào)文當(dāng)中的ACK被設(shè)置為1,表明該報(bào)文可以對(duì)收到的報(bào)文進(jìn)行確認(rèn)。
一般除了第一個(gè)請(qǐng)求報(bào)文沒(méi)有設(shè)置ACK以外,其余報(bào)文基本都會(huì)設(shè)置ACK,因?yàn)榘l(fā)送出去的數(shù)據(jù)本身就對(duì)對(duì)方發(fā)送過(guò)來(lái)的數(shù)據(jù)具有一定的確認(rèn)能力,因此雙方在進(jìn)行數(shù)據(jù)通信時(shí),可以順便對(duì)對(duì)方上一次發(fā)送的數(shù)據(jù)進(jìn)行響應(yīng)。
- PSH
報(bào)文當(dāng)中的PSH被設(shè)置為1,是在告訴對(duì)方上層盡快去取走數(shù)據(jù)。
因?yàn)榭赡芙邮辗降拇翱谥当容^小,而發(fā)送發(fā)就需要阻塞等待接收方取走緩沖區(qū)的數(shù)據(jù)后才能發(fā)送,那么此時(shí)就可以用PSH標(biāo)志位來(lái)催促。
- URG & 16位緊急指針
報(bào)文當(dāng)中URG被設(shè)置為1,是告訴對(duì)方這個(gè)數(shù)據(jù)是要特殊盡快處理。
因?yàn)門(mén)CP是可靠傳輸,所以數(shù)據(jù)段一定是有序的被接收方收到,但是如果有數(shù)據(jù)段想要插隊(duì)就可以設(shè)置URG。
這里要注意并不是說(shuō)這個(gè)數(shù)據(jù)段的有效載荷的所有部分都要被緊急處理,可能只是一小部分,那么怎么找到位置呢?
TCP報(bào)頭有一個(gè)字段是緊急指針。它填寫(xiě)的是偏移量,就可以找到緊急數(shù)據(jù)。因?yàn)榫o急指針只有一個(gè),它只能標(biāo)識(shí)數(shù)據(jù)段中的一個(gè)位置,因此緊急數(shù)據(jù)只能發(fā)送一個(gè)字節(jié)。
URG一般用來(lái)發(fā)送帶外數(shù)據(jù),它不用走TCP流,因?yàn)榻邮辗街苯犹幚?。比方說(shuō)我們現(xiàn)在發(fā)了很多數(shù)據(jù),對(duì)方正在處理,但是我們突然發(fā)現(xiàn)不需要這些數(shù)據(jù)了,此時(shí)就可以發(fā)送緊急帶外數(shù)據(jù),把套接字關(guān)了。
- RST
報(bào)文當(dāng)中的RST被設(shè)置為1,表示需要讓對(duì)方重新建立連接。
在通信雙方在連接未建立好的情況下,一方向另一方發(fā)數(shù)據(jù),此時(shí)另一方發(fā)送的響應(yīng)報(bào)文當(dāng)中的RST標(biāo)志位就會(huì)被置1,表示要求對(duì)方重新建立連接。
還有一種情況是服務(wù)端網(wǎng)線被把了,連接被斷開(kāi)了,但是客戶端不知道,還會(huì)發(fā)消息,這時(shí)服務(wù)端就會(huì)把RST設(shè)置為1,讓客戶端建立一個(gè)新連接。
二、確認(rèn)應(yīng)答機(jī)制(ACK)
TCP保證可靠性的機(jī)制之一就是確認(rèn)應(yīng)答機(jī)制。
確認(rèn)應(yīng)答機(jī)制是靠TCP報(bào)頭中的32位序號(hào)和32位確認(rèn)序號(hào)實(shí)現(xiàn)的,收到的確認(rèn)應(yīng)答說(shuō)明該序號(hào)之前的數(shù)據(jù)全部被收到了。
- 如何理解TCP將每個(gè)字節(jié)的數(shù)據(jù)都進(jìn)行了編號(hào)?
我們可以把傳輸層的發(fā)送緩沖區(qū)看成一個(gè)數(shù)組,當(dāng)我們把應(yīng)用層的數(shù)據(jù)拷貝到發(fā)送緩沖區(qū)的時(shí)候,每個(gè)字節(jié)的數(shù)據(jù)就天然的有了一個(gè)編號(hào)(下標(biāo)),只不過(guò)這個(gè)下標(biāo)不是從0開(kāi)始的,而是從1開(kāi)始往后遞增的。。
發(fā)送方發(fā)送數(shù)據(jù)時(shí)報(bào)頭當(dāng)中所填的序號(hào),實(shí)際就是發(fā)送的若干字節(jié)數(shù)據(jù)當(dāng)中,首個(gè)字節(jié)數(shù)據(jù)在發(fā)送緩沖區(qū)當(dāng)中對(duì)應(yīng)的下標(biāo)。
接收方接收到數(shù)據(jù)進(jìn)行響應(yīng)時(shí),響應(yīng)報(bào)頭當(dāng)中的確認(rèn)序號(hào)實(shí)際就是,接收緩沖區(qū)中接收到的最后一個(gè)有效數(shù)據(jù)的下一個(gè)位置所對(duì)應(yīng)的下標(biāo)。
當(dāng)發(fā)送方收到接收方的響應(yīng)后,就可以從下標(biāo)為確認(rèn)序號(hào)的位置繼續(xù)進(jìn)行發(fā)送了。
2.1 超時(shí)重傳機(jī)制
2.1.1 丟包的兩種情況
- 情況一
發(fā)送的數(shù)據(jù)報(bào)文丟失了,此時(shí)發(fā)送端在一定時(shí)間內(nèi)收不到對(duì)應(yīng)的響應(yīng)報(bào)文,就會(huì)進(jìn)行超時(shí)重傳。
- 情況二
對(duì)方發(fā)來(lái)的響應(yīng)報(bào)文丟包了,此時(shí)發(fā)送端也會(huì)因?yàn)槭詹坏綄?duì)應(yīng)的響應(yīng)報(bào)文,而進(jìn)行超時(shí)重傳。
當(dāng)出現(xiàn)丟包情況的時(shí)候,發(fā)送方是不會(huì)知道究竟是數(shù)據(jù)段發(fā)送的時(shí)候丟包了還是確認(rèn)應(yīng)答的時(shí)候丟包。所以發(fā)送方只能進(jìn)行超時(shí)重傳。
那么如果是第二種丟包情況,接收方就可能會(huì)收到份同樣的數(shù)據(jù)。因?yàn)橹貜?fù)的報(bào)文也是不可靠的一種,所以主機(jī)B需要進(jìn)行去重(通過(guò)序號(hào))。
因?yàn)樾枰瑫r(shí)重傳,所以數(shù)據(jù)發(fā)送出去后不會(huì)立即清除,而是保留一段時(shí)間。直到收到該數(shù)據(jù)的響應(yīng)報(bào)文后,發(fā)送緩沖區(qū)中的這部分?jǐn)?shù)據(jù)才可以被刪除或覆蓋。
2.1.2 超時(shí)重傳的等待時(shí)間
我們通過(guò)超時(shí)來(lái)判斷是否丟包,那么這個(gè)時(shí)間到底是多久呢?
我們知道數(shù)據(jù)發(fā)送的時(shí)間是由網(wǎng)絡(luò)狀況決定的,而網(wǎng)絡(luò)會(huì)因?yàn)榄h(huán)境的變化不斷變化。所以超時(shí)重傳的時(shí)間一定不是固定的。
TCP為了保證無(wú)論在任何環(huán)境下都能有比較高性能的通信,因此會(huì)動(dòng)態(tài)計(jì)算這個(gè)最大超時(shí)時(shí)間:
Linux中(BSD Unix和Windows也是如此),超時(shí)以500ms為一個(gè)單位進(jìn)行控制,每次判定超時(shí)重發(fā)的超時(shí)時(shí)間都是500ms的整數(shù)倍。
如果重發(fā)一次之后,仍然得不到應(yīng)答,下一次重傳的等待時(shí)間就是2 × 500ms。
如果仍然得不到應(yīng)答,那么下一次重傳的等待時(shí)間就是4 × 500ms。以此類(lèi)推,以指數(shù)的形式遞增。
當(dāng)累計(jì)到一定的重傳次數(shù)后,TCP就會(huì)認(rèn)為是網(wǎng)絡(luò)或?qū)Χ酥鳈C(jī)出現(xiàn)了異常,進(jìn)而強(qiáng)轉(zhuǎn)關(guān)閉連接。
三、連接管理機(jī)制
3.1 面向連接相關(guān)概念
面向連接是通過(guò)要連接的兩臺(tái)主機(jī)分別在自己的主機(jī)上開(kāi)辟一塊區(qū)域,然后通過(guò)TCP協(xié)議來(lái)共同維護(hù)這兩塊區(qū)域,來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)傳輸?shù)目煽啃浴K裕?strong>面向連接就是為了保證數(shù)據(jù)的可靠性。
這樣說(shuō)可能還不是很好理解,那么在對(duì)比一下面向無(wú)連接理解一下。UDP協(xié)議就是典型的無(wú)連接協(xié)議,在兩臺(tái)主機(jī)之間網(wǎng)絡(luò)通信時(shí),不需要知道目標(biāo)主機(jī)ip和目標(biāo)端口是否存在,可以按照定義的ip和端口直接發(fā)送到網(wǎng)絡(luò)中,而面向連接則是先根據(jù)給定的目標(biāo)ip和端口號(hào)發(fā)送到網(wǎng)絡(luò)中一些消息來(lái)確認(rèn)目標(biāo)主機(jī)是否存在,如果不存在則不能完成接下來(lái)的網(wǎng)絡(luò)通信。
所以,面向連接是需要先建立連接才能進(jìn)行網(wǎng)絡(luò)通信的,建立連接就是確定對(duì)方存在并協(xié)商好一些控制量來(lái)確保接下來(lái)的通信是可靠的。
- 無(wú)連接協(xié)議和面向連接協(xié)議的概念
無(wú)連接協(xié)議中的分組被稱為數(shù)據(jù)報(bào),每個(gè)分組都是獨(dú)立尋址,并由應(yīng)用程序發(fā)送的。從協(xié)議的角度來(lái)看,每個(gè)數(shù)據(jù)報(bào)都是一個(gè)獨(dú)立的實(shí)體,與在兩個(gè)相同的對(duì)等實(shí)體之間傳送的任何其他數(shù)據(jù)報(bào)都沒(méi)有關(guān)系,這就意味著協(xié)議很可能是不可靠的。也就是說(shuō),網(wǎng)絡(luò)會(huì)盡最大努力傳送每一個(gè)數(shù)據(jù)報(bào),但并不保證數(shù)據(jù)報(bào)不丟失、不延遲或者不錯(cuò)序傳輸。
??另一方面,面向連接的協(xié)議則維護(hù)了分組之間的狀態(tài),使用這種協(xié)議的應(yīng)用程序通常都會(huì)進(jìn)行長(zhǎng)期的對(duì)話。記住這些狀態(tài),協(xié)議就可以提供可靠的傳輸。比如,發(fā)送端可以記住哪些數(shù)據(jù)已經(jīng)發(fā)送出去了但還未被確認(rèn),以及數(shù)據(jù)是什么時(shí)候發(fā)送的。如果在某段時(shí)間間隔內(nèi)沒(méi)有收到確認(rèn),發(fā)送端可以重傳數(shù)據(jù)。接收端可以記住已經(jīng)收到了哪些數(shù)據(jù),并將重復(fù)的數(shù)據(jù)丟棄。如果分組不是按序到達(dá)的,接收端可以將其保存下來(lái),直到邏輯上先于它的分組到達(dá)為止。
??典型的面向連接協(xié)議有三個(gè)階段。第一階段,在對(duì)等實(shí)體間建立連接。接下來(lái)是數(shù)據(jù)傳輸階段,在這個(gè)階段中,數(shù)據(jù)在對(duì)等實(shí)體間傳輸。最后,當(dāng)對(duì)等實(shí)體完成數(shù)據(jù)傳輸時(shí),連接被拆除。
??一種標(biāo)準(zhǔn)的類(lèi)比是:使用無(wú)連接協(xié)議就像寄信,而使用面向連接的協(xié)議就像打電話。
- TCP為什么要建立連接?
因?yàn)橐WC可靠性,連接不能直接保證可靠性。
只要建立了連接,就會(huì)有連接結(jié)構(gòu)體,里面包含了超時(shí)重傳、按序到達(dá)、流量控制、擁塞控制等等策略以及通信狀態(tài)和報(bào)文屬性等等。連接結(jié)構(gòu)體就是保證數(shù)據(jù)可靠性的基礎(chǔ)。而三次握手是建立連接結(jié)構(gòu)體的基礎(chǔ),所以三次握手間接的保證了可靠性。
而UDP不需要通信狀態(tài)以及報(bào)文屬性等等,所以不需要建立連接。
3.2 三次握手
雙方在進(jìn)行TCP通信之前需要先建立連接,建立連接的這個(gè)過(guò)程我們稱之為三次握手。
第一次握手:客戶端向服務(wù)器發(fā)送的報(bào)文當(dāng)中的SYN位被設(shè)置為1,表示請(qǐng)求與服務(wù)器建立連接。
第二次握手:服務(wù)器收到客戶端發(fā)來(lái)的連接請(qǐng)求報(bào)文后,緊接著向客戶端發(fā)起連接建立請(qǐng)求并對(duì)客戶端發(fā)來(lái)的連接請(qǐng)求進(jìn)行響應(yīng),此時(shí)服務(wù)器向客戶端發(fā)送的報(bào)文當(dāng)中的SYN位和ACK位均被設(shè)置為1。
第三次握手:客戶端收到服務(wù)器發(fā)來(lái)的報(bào)文后,得知服務(wù)器收到了自己發(fā)送的連接建立請(qǐng)求,并請(qǐng)求和自己建立連接,最后客戶端再向服務(wù)器發(fā)來(lái)的報(bào)文進(jìn)行響應(yīng)。
- 為什么要三次握手?
建立連接時(shí)不是百分之百成功的,三次握手的任何一次都有可能丟包,前兩次握手是能夠保證被對(duì)方收到的,因?yàn)樗鼈兌加袘?yīng)答,如果沒(méi)有,大不了超時(shí)重傳,但是如果是第三次ACK應(yīng)答丟了呢?
當(dāng)客戶端發(fā)送ACK應(yīng)答的一瞬間,它就會(huì)認(rèn)為三次握手已經(jīng)建立成功了,此時(shí)如果ACK應(yīng)答丟了,此時(shí)就會(huì)連接建立失敗,但是根本不用擔(dān)心,有解決方案:
例如服務(wù)端沒(méi)有收到應(yīng)答,它就會(huì)重傳第二次握手,客戶端就會(huì)意識(shí)到鏈接沒(méi)有被建立成功。
另外算客戶端已經(jīng)發(fā)送了數(shù)據(jù),因?yàn)橹挥腥挝帐殖晒α瞬拍馨l(fā)送消息,所以服務(wù)端就會(huì)返回RST報(bào)文要求客戶端重新建立連接。
- 一次和兩次握手行不行?
先說(shuō)說(shuō)一次,一次就是客戶端發(fā)送連接請(qǐng)求后就認(rèn)為連接建立好了,服務(wù)端就會(huì)維護(hù)這個(gè)連接。那么如果客戶端寫(xiě)一個(gè)多線程不斷向服務(wù)端發(fā)送連接請(qǐng)求,服務(wù)端就會(huì)認(rèn)為這些連接都建立好了,服務(wù)端就會(huì)維護(hù)這些鏈接,如果鏈接過(guò)多了就會(huì)導(dǎo)致資源被占滿,這個(gè)情況就叫做SYN洪水。其次也無(wú)法驗(yàn)證全雙工通信信道是通暢的(客戶端無(wú)法保證自己發(fā)送了消息被服務(wù)端收到),所以一次握手是不可能完成連接的。
那么兩次握手呢?在第二次握手發(fā)出報(bào)文的瞬間服務(wù)端就認(rèn)為連接建立好了,可能這個(gè)報(bào)文客戶端壓根就沒(méi)收到,所以就會(huì)產(chǎn)生一次握手同樣的問(wèn)題(SYN洪水)。其次也無(wú)法驗(yàn)證全雙工通信信道是通暢的(服務(wù)端不能證明自己能發(fā)送消息被對(duì)方收到)。
上面會(huì)導(dǎo)致單機(jī)攻擊服務(wù)器的本質(zhì)原因是客戶端還沒(méi)有建立連接時(shí)服務(wù)端已經(jīng)建立好連接了。所以必須讓客戶端先建立連接,服務(wù)端再建立連接。
現(xiàn)在就可以說(shuō)明為什么要三次握手了:
三次握手是用最小的成本驗(yàn)證全雙工通信信道是通暢的。
要想服務(wù)端建立連接,客戶端必須先建立連接,所以可以有效的規(guī)避單主機(jī)對(duì)服務(wù)器攻擊問(wèn)題。
-
ddos攻擊
這里要注意三次握手并不能解決安全問(wèn)題,比方說(shuō)大量的主機(jī)同時(shí)發(fā)送TCP請(qǐng)求也會(huì)導(dǎo)致服務(wù)端崩潰。假設(shè)黑客黑掉了很多主機(jī)同時(shí)給服務(wù)端發(fā)送TCP連接請(qǐng)求:
此時(shí)再有客戶端發(fā)送連接請(qǐng)求,服務(wù)端就提供不了服務(wù)了(連不上),這種攻擊手段就是ddos攻擊(服務(wù)拒絕攻擊)。
- 四次握手行不行?
可以是可以,但是沒(méi)必要,會(huì)降低效率。
服務(wù)端是把第二次握手的SYN和ACK分開(kāi)發(fā)送,這兩個(gè)既然可以合并發(fā)就沒(méi)必要分開(kāi)分兩次發(fā)送。出于優(yōu)化目的,四次握手中的二、三可以合并。
- 三次握手的狀態(tài)變化
最開(kāi)始時(shí)客戶端和服務(wù)器都處于CLOSED狀態(tài)。
1?? 服務(wù)器為了能夠接收客戶端發(fā)來(lái)的連接請(qǐng)求,需要由CLOSED狀態(tài)變?yōu)長(zhǎng)ISTEN狀態(tài)。
2?? 此時(shí)客戶端就可以向服務(wù)器發(fā)起三次握手了,當(dāng)客戶端發(fā)起第一次握手后,狀態(tài)變?yōu)镾YN_SENT狀態(tài)。
3?? 處于LISTEN狀態(tài)的服務(wù)器收到客戶端的連接請(qǐng)求后,將該連接放入內(nèi)核等待隊(duì)列中,并向客戶端發(fā)起第二次握手,此時(shí)服務(wù)器的狀態(tài)變?yōu)镾YN_RCVD。
4?? 當(dāng)客戶端收到服務(wù)器發(fā)來(lái)的第二次握手后,緊接著向服務(wù)器發(fā)送最后一次握手,此時(shí)客戶端的連接已經(jīng)建立,狀態(tài)變?yōu)镋STABLISHED。
5?? 而服務(wù)器收到客戶端發(fā)來(lái)的最后一次握手后,連接也建立成功,此時(shí)服務(wù)器的狀態(tài)也變成ESTABLISHED。
- 套接字和三次握手之間的關(guān)系
在客戶端發(fā)起連接建立請(qǐng)求之前,服務(wù)器需要先進(jìn)入LISTEN狀態(tài),此時(shí)就需要服務(wù)器調(diào)用對(duì)應(yīng)listen函數(shù)設(shè)置套接字屬性。
當(dāng)服務(wù)器進(jìn)入LISTEN狀態(tài)后,客戶端就可以向服務(wù)器發(fā)起三次握手了,此時(shí)客戶端對(duì)應(yīng)調(diào)用的就是connect函數(shù)。
需要注意的是,connect函數(shù)不參與底層的三次握手,connect函數(shù)的作用只是發(fā)起三次握手。當(dāng)connect函數(shù)返回時(shí),要么是底層已經(jīng)成功完成了三次握手連接建立成功,要么是底層三次握手失敗。
如果服務(wù)器端與客戶端成功完成了三次握手,此時(shí)在服務(wù)器端就會(huì)建立一個(gè)連接,但這個(gè)連接在內(nèi)核的等待隊(duì)列當(dāng)中,服務(wù)器端需要通過(guò)調(diào)用accept函數(shù)將這個(gè)建立好的連接獲取上來(lái)。
當(dāng)服務(wù)器端將建立好的連接獲取上來(lái)后,雙方就可以通過(guò)調(diào)用read/recv函數(shù)和write/send函數(shù)進(jìn)行數(shù)據(jù)交互了。
3.3 四次揮手
由于雙方維護(hù)連接都是需要成本的,因此當(dāng)雙方TCP通信結(jié)束之后就需要斷開(kāi)連接,斷開(kāi)連接的這個(gè)過(guò)程我們稱之為四次揮手。
哪邊不想給對(duì)方發(fā)消息了,就要發(fā)送斷開(kāi)連接請(qǐng)求,比如說(shuō)客戶端要斷開(kāi)連接:
客戶端發(fā)送斷開(kāi)連接請(qǐng)求,服務(wù)端返回ACK應(yīng)答,這就已經(jīng)兩次揮手了。
服務(wù)端也要斷開(kāi)連接,發(fā)送請(qǐng)求,客戶端返回ACK應(yīng)答,這就是四次揮手。
這里有一個(gè)問(wèn)題,既然前面客戶端都已經(jīng)說(shuō)明不給服務(wù)端發(fā)送數(shù)據(jù)了,為什么后邊還會(huì)發(fā)送確認(rèn)應(yīng)答呢?
注意這里的不發(fā)送數(shù)據(jù)的數(shù)據(jù)指的是用戶數(shù)據(jù)(應(yīng)用層不發(fā)數(shù)據(jù)了)。并不代表底層沒(méi)有報(bào)文交互。
注意這里的二三次揮手是有可能合并為一次的。
- 四次揮手時(shí)的狀態(tài)變化
在揮手前客戶端和服務(wù)器都處于連接建立后的ESTABLISHED狀態(tài)。
1?? 客戶端為了與服務(wù)器斷開(kāi)連接主動(dòng)向服務(wù)器發(fā)起連接斷開(kāi)請(qǐng)求,此時(shí)客戶端的狀態(tài)變?yōu)镕IN_WAIT_1。
2?? 服務(wù)器收到客戶端發(fā)來(lái)的連接斷開(kāi)請(qǐng)求后對(duì)其進(jìn)行響應(yīng),此時(shí)服務(wù)器的狀態(tài)變?yōu)镃LOSE_WAIT。
3?? 當(dāng)服務(wù)器沒(méi)有數(shù)據(jù)需要發(fā)送給客戶端的時(shí),服務(wù)器會(huì)向客戶端發(fā)起斷開(kāi)連接請(qǐng)求,等待最后一個(gè)ACK到來(lái),此時(shí)服務(wù)器的狀態(tài)變?yōu)長(zhǎng)ASE_ACK。
4?? 客戶端收到服務(wù)器發(fā)來(lái)的第三次揮手后,會(huì)向服務(wù)器發(fā)送最后一個(gè)響應(yīng)報(bào)文,此時(shí)客戶端進(jìn)入TIME_WAIT狀態(tài)。
5?? 當(dāng)服務(wù)器收到客戶端發(fā)來(lái)的最后一個(gè)響應(yīng)報(bào)文時(shí),服務(wù)器會(huì)徹底關(guān)閉連接,變?yōu)镃LOSED狀態(tài)。
6?? 而客戶端則會(huì)等待一個(gè)2MSL(Maximum Segment Lifetime,報(bào)文最大生存時(shí)間)才會(huì)進(jìn)入CLOSED狀態(tài)。
- 套接字和四次揮手之間的關(guān)系
客戶端發(fā)起斷開(kāi)連接請(qǐng)求,對(duì)應(yīng)就是客戶端主動(dòng)調(diào)用close函數(shù)關(guān)閉套接字。
服務(wù)器發(fā)起斷開(kāi)連接請(qǐng)求,對(duì)應(yīng)就是服務(wù)器主動(dòng)調(diào)用close函數(shù)關(guān)閉套接字。
一個(gè)close對(duì)應(yīng)的就是兩次揮手,雙方都要調(diào)用close,因此就是四次揮手。
主動(dòng)斷開(kāi)連接的一方最終狀態(tài)是TIME_WAIT
被動(dòng)斷開(kāi)連接的一方兩次揮手完成后的狀態(tài)是CLOSE_WAIT
我們主要研究的就是這兩個(gè)狀態(tài):
- CLOSE_WAIT狀態(tài)
雙方在進(jìn)行四次揮手時(shí),如果只有客戶端調(diào)用了close函數(shù),而服務(wù)器不調(diào)用close函數(shù)(不會(huì)發(fā)送FIN),此時(shí)服務(wù)器就會(huì)進(jìn)入CLOSE_WAIT狀態(tài),而客戶端則會(huì)進(jìn)入到FIN_WAIT_2狀態(tài)。
如果服務(wù)器沒(méi)有主動(dòng)關(guān)閉不需要的文件描述符,此時(shí)在服務(wù)器端就會(huì)存在大量處于CLOSE_WAIT狀態(tài)的連接,而每個(gè)連接都會(huì)占用服務(wù)器的資源,最終就會(huì)導(dǎo)致服務(wù)器可用資源越來(lái)越少。
因此在編寫(xiě)網(wǎng)絡(luò)套接字代碼時(shí),如果發(fā)現(xiàn)服務(wù)器端存在大量處于CLOSE_WAIT狀態(tài)的連接,此時(shí)就可以檢查一下是不是服務(wù)器沒(méi)有及時(shí)調(diào)用close函數(shù)關(guān)閉對(duì)應(yīng)的文件描述符。
- TIME_WAIT狀態(tài)
次揮手前三次如果發(fā)生了丟包情況,我們都可以利用超時(shí)重傳機(jī)制,最擔(dān)心的自然是第四次ACK應(yīng)答時(shí)丟包
如果客戶端在發(fā)出第四次揮手后立即進(jìn)入CLOSED狀態(tài),此時(shí)服務(wù)器雖然進(jìn)行了超時(shí)重傳,但已經(jīng)得不到客戶端的響應(yīng)了,因?yàn)?strong>客戶端已經(jīng)將連接關(guān)閉了。
服務(wù)器在經(jīng)過(guò)若干次超時(shí)重發(fā)后得不到響應(yīng),最終也一定會(huì)將對(duì)應(yīng)的連接關(guān)閉,但在服務(wù)器不斷進(jìn)行超時(shí)重傳期間還需要維護(hù)這條廢棄的連接,這樣對(duì)服務(wù)器是非常不友好的。
為了避免這種情況,因此客戶端在四次揮手后沒(méi)有立即進(jìn)入CLOSED狀態(tài),而是進(jìn)入到了TIME_WAIT狀態(tài)進(jìn)行等待,此時(shí)要是第四次揮手的報(bào)文丟包了,客戶端也能收到服務(wù)器重發(fā)的報(bào)文然后進(jìn)行響應(yīng)。
所以TIME_WAIT會(huì)保證最后一個(gè)ACK應(yīng)答盡量被對(duì)方收到,而且可能斷開(kāi)之前發(fā)送的報(bào)文還滯留在網(wǎng)絡(luò)中,那么TIME_WAIT就可以保證雙方通信信道上的數(shù)據(jù)在網(wǎng)絡(luò)中盡可能的消散。
而TIME_WAIT狀態(tài)的時(shí)間過(guò)長(zhǎng),也會(huì)自動(dòng)關(guān)閉連接,那么這個(gè)時(shí)間多長(zhǎng)呢?
- TIME_WAIT的等待時(shí)長(zhǎng)
TCP協(xié)議規(guī)定,主動(dòng)關(guān)閉連接的一方在四次揮手后要處于TIME_WAIT狀態(tài),等待兩個(gè)MSL(報(bào)文最大生存時(shí)間)的時(shí)間才能進(jìn)入CLOSED狀態(tài)。
我們把從發(fā)送方到接收方經(jīng)過(guò)的最大時(shí)間叫做MSL。
TIME_WAIT的等待時(shí)長(zhǎng)設(shè)置為兩個(gè)MSL的原因:
MSL是TCP報(bào)文的最大生存時(shí)間,因此TIME_WAIT狀態(tài)持續(xù)存在2MSL的話,就能保證在兩個(gè)傳輸方向上的尚未被接收或遲到的報(bào)文段都已經(jīng)消失。
同時(shí)也是在理論上保證最后一個(gè)報(bào)文可靠到達(dá)的時(shí)間。
Centos7上默認(rèn)配置的值是60s
3.4 bind綁定失?。ǘ丝趶?fù)用)
- bind綁定失敗原因
在之前的代碼里發(fā)現(xiàn)如果是服務(wù)端主動(dòng)斷開(kāi)連接,就會(huì)導(dǎo)致一段時(shí)間內(nèi)無(wú)法bind,就是因?yàn)榉?wù)器是CLOSE_WAIT狀態(tài),該端口和連接依舊存在,所以會(huì)綁定失?。ǘ丝诒徽加茫?/p>
服務(wù)器不能立即重啟的危害:
比方說(shuō)雙十一的時(shí)候,連接過(guò)多導(dǎo)致服務(wù)器掛掉了,此時(shí) 我們想要立即重啟卻要等很久(60S),那么就會(huì)造成巨大的損失。
那么怎么解決這個(gè)問(wèn)題呢?
- 設(shè)置套接字復(fù)用
使用setsockopt()設(shè)置socket描述符的 選項(xiàng)
SO_REUSEADDR
為1, 表示允許創(chuàng)建端口號(hào)相同但I(xiàn)P地址不同的多個(gè)socket描述符。
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_ REUSEADDR, &opt, sizeof(opt)) ;
四、流量控制
TCP支持根據(jù)接收端的接收數(shù)據(jù)的能力來(lái)決定發(fā)送端發(fā)送數(shù)據(jù)的速度,這個(gè)機(jī)制叫做流量控制。
上面在講16位窗口大小的時(shí)候就說(shuō)了傳輸數(shù)據(jù)的時(shí)候速度要適中,所以報(bào)頭中有16位窗口大小,來(lái)控制傳輸速度,通過(guò)填寫(xiě)16位窗口大小告訴對(duì)端自己的接收能力(接收緩沖區(qū)還剩多少)。
但是發(fā)送方怎么在第一次就知道對(duì)方的接受能力呢?
在通信之前,就已經(jīng)三次握手了,所以在握手期間,就可以互相交換窗口大小了。
關(guān)于窗口大小如何控制傳輸速度已經(jīng)在上面講過(guò),不做贅述。
這里并補(bǔ)充一點(diǎn):
當(dāng)發(fā)送端得知接收端接收數(shù)據(jù)的能力為0時(shí)會(huì)停止發(fā)送數(shù)據(jù),此時(shí)發(fā)送端會(huì)通過(guò)以下兩種方式來(lái)得知何時(shí)可以繼續(xù)發(fā)送數(shù)據(jù):
等待告知:接收端上層將接收緩沖區(qū)當(dāng)中的數(shù)據(jù)讀走后,接收端向發(fā)送端發(fā)送一個(gè)TCP報(bào)文,主動(dòng)將自己的窗口大小告知發(fā)送端,發(fā)送端得知接收端的接收緩沖區(qū)有空間后就可以繼續(xù)發(fā)送數(shù)據(jù)了。
主動(dòng)詢問(wèn):發(fā)送端每隔一段時(shí)間向接收端發(fā)送報(bào)文(窗口探測(cè)),該報(bào)文不攜帶有效數(shù)據(jù),只是為了詢問(wèn)發(fā)送端的窗口大小,直到接收端的接收緩沖區(qū)有空間后發(fā)送端就可以繼續(xù)發(fā)送數(shù)據(jù)了。
這兩種策略在實(shí)際中是同時(shí)使用的,哪個(gè)先到就處理哪個(gè)。
五、滑動(dòng)窗口
在我們發(fā)送數(shù)據(jù)但是沒(méi)收到應(yīng)答之前,我們必須把數(shù)據(jù)暫時(shí)保存起來(lái),以支持后續(xù)可能出現(xiàn)的超時(shí)重傳。那么保存在哪里呢?
答案是發(fā)送緩沖區(qū)
前面說(shuō)過(guò),多個(gè)報(bào)文一般是并行發(fā)送,即還沒(méi)收到應(yīng)答,下一個(gè)報(bào)文就已經(jīng)發(fā)送出去了,目的是為了提高效率。
那么我們就可以把發(fā)送緩沖區(qū)分成三個(gè)部分:
- 已經(jīng)發(fā)送并且已經(jīng)收到ACK的數(shù)據(jù)。
- 已經(jīng)發(fā)送還但沒(méi)有收到ACK的數(shù)據(jù)。
- 還沒(méi)有發(fā)送的數(shù)據(jù)。
滑動(dòng)窗口的本質(zhì)就是發(fā)送緩沖區(qū)的一部分。 通過(guò)不斷地滑動(dòng)來(lái)重新劃分三段區(qū)間。
- 如何理解滑動(dòng)窗口?
把緩沖區(qū)看成一個(gè)數(shù)組,那么滑動(dòng)窗口的移動(dòng)其實(shí)就是下標(biāo)進(jìn)行更新。
- 滑動(dòng)窗口的大小
滑動(dòng)窗口的大小和對(duì)方的接收能力有關(guān),未來(lái)不管怎么滑動(dòng),都要保證對(duì)方能夠正常接收(滑動(dòng)窗口大小 <= 對(duì)方接受能力)。具體多大在后面講擁塞控制的時(shí)候會(huì)講。
- 滑動(dòng)窗口一定會(huì)整體右移嗎?
可能向右滑動(dòng),可能保持不變。因?yàn)榭赡軘?shù)據(jù)在對(duì)方的接收緩沖區(qū)遲遲沒(méi)有被拿走。
- 滑動(dòng)窗口如何滑動(dòng)?
當(dāng)發(fā)送端收到對(duì)方的響應(yīng)時(shí),如果響應(yīng)當(dāng)中的確認(rèn)序號(hào)為ACK_SEQ
,窗口大小為tcp_win
,此時(shí)就可以將win_start更新為ACK_SEQ
,而將win_end更新為win_start + tcp_win
。
那么如果對(duì)方的上層一直不取走數(shù)據(jù),發(fā)送發(fā)卻一直發(fā),就會(huì)導(dǎo)致tcp_win
越來(lái)越小,也就是滑動(dòng)窗口的左側(cè)一直向后移動(dòng),右側(cè)卻不變,最終滑動(dòng)窗口會(huì)變?yōu)?。
- 如果收到的ACK不是最左側(cè)數(shù)據(jù)的確認(rèn),而是中間的怎么辦?
因?yàn)門(mén)CP是可靠傳輸,不可能出現(xiàn)亂序,所以如果收到了中間數(shù)據(jù)的應(yīng)答,一定是發(fā)生了丟包。
- 丟包問(wèn)題
丟包可以分為兩個(gè)情況:
1?? 數(shù)據(jù)沒(méi)丟,ACK應(yīng)答丟了
根據(jù)確認(rèn)序號(hào)的定義,如果收到的是3001,那么說(shuō)明3000以前的數(shù)據(jù)全部都收到了,那么就把win_start移動(dòng)到3001即可。
2?? 數(shù)據(jù)真的丟了
當(dāng)1001-2000的數(shù)據(jù)包丟失后,發(fā)送端會(huì)一直收到確認(rèn)序號(hào)為1001的響應(yīng)報(bào)文,就是在提醒發(fā)送端“下一次應(yīng)該從序號(hào)為1001的字節(jié)數(shù)據(jù)開(kāi)始發(fā)送”。
而如果連續(xù)收到三個(gè)同樣的確認(rèn)序號(hào),就會(huì)觸發(fā)重傳機(jī)制。 這也叫做快重傳:
快重傳是能夠快速進(jìn)行數(shù)據(jù)的重發(fā),當(dāng)發(fā)送端連續(xù)收到三次相同的應(yīng)答時(shí)就會(huì)觸發(fā)快重傳,而不像超時(shí)重傳一樣需要通過(guò)設(shè)置重傳定時(shí)器,在固定的時(shí)間后才會(huì)進(jìn)行重傳。
總結(jié)一下:
滑動(dòng)窗口的左端就是通過(guò)確認(rèn)序號(hào)確定的,右端是通過(guò)左端和對(duì)方接收緩沖區(qū)的剩余空間決定的。
- 滑動(dòng)窗口空間問(wèn)題
滑動(dòng)窗口一直向右滑動(dòng),那么總有空間用完的時(shí)候,該如何處理呢?
發(fā)送緩沖區(qū)被內(nèi)核組織成了環(huán)形結(jié)構(gòu)。
六、擁塞控制
1000個(gè)報(bào)文丟掉一兩個(gè)很正常,重復(fù)發(fā)即可,但是如果1000個(gè)報(bào)文有999個(gè)都丟了,那我們還要重傳么?
打個(gè)比方,考試一個(gè)班四十個(gè)人如果只有一個(gè)人掛了,那大概率是這個(gè)人的問(wèn)題,如果掛了39個(gè),那還是學(xué)生的問(wèn)題嗎?
針對(duì)這種大面積的丟包情況,TCP就會(huì)考慮是網(wǎng)絡(luò)擁塞問(wèn)題,此時(shí)重傳就沒(méi)什么用了,重傳也只會(huì)加重網(wǎng)絡(luò)故障問(wèn)題。
- 如何解決網(wǎng)絡(luò)擁塞問(wèn)題?
當(dāng)網(wǎng)絡(luò)出現(xiàn)擁塞問(wèn)題時(shí),通信雙方雖然不能提出特別有效的解決方案,但雙方主機(jī)可以做到不加重網(wǎng)絡(luò)的負(fù)擔(dān)。
雙方通信時(shí)如果出現(xiàn)大量丟包,不應(yīng)該立即將這些報(bào)文進(jìn)行重傳,而應(yīng)該少發(fā)數(shù)據(jù)甚至不發(fā)數(shù)據(jù),等待網(wǎng)絡(luò)狀況恢復(fù)后雙方再慢慢恢復(fù)數(shù)據(jù)的傳輸速率。
需要注意的是,網(wǎng)絡(luò)擁塞時(shí)影響的不只是一臺(tái)主機(jī),而幾乎是該網(wǎng)絡(luò)當(dāng)中的所有主機(jī),此時(shí)所有使用TCP傳輸控制協(xié)議的主機(jī)都會(huì)執(zhí)行擁塞避免算法。
- 擁塞控制
TCP引入了慢啟動(dòng)機(jī)制,在剛開(kāi)始通信時(shí)先發(fā)少量的數(shù)據(jù)探探路,摸清當(dāng)前的網(wǎng)絡(luò)擁堵?tīng)顟B(tài),再?zèng)Q定按照多大的速度傳輸數(shù)據(jù)。
在講慢啟動(dòng)機(jī)制之前先引入一個(gè)概念:擁塞窗口
其實(shí)就是一個(gè)數(shù)字,再超過(guò)這個(gè)數(shù)字的時(shí)候就可能引發(fā)網(wǎng)絡(luò)擁塞問(wèn)題。
最開(kāi)始的時(shí)候定義為1,每次接收到一個(gè)ACK應(yīng)答,就加1,每次發(fā)送數(shù)據(jù)包的時(shí)候,將擁塞窗口和接收端主機(jī)反饋的窗口大小做比較,取較小的值作為實(shí)際發(fā)送數(shù)據(jù)的窗口大小,即滑動(dòng)窗口的大小。
滑動(dòng)窗口大小 = min(擁塞窗口,窗口大?。▽?duì)端的接受能力))
每收到一個(gè)ACK應(yīng)答擁塞窗口的值就加1,此時(shí)擁塞窗口就是以指數(shù)級(jí)別進(jìn)行增長(zhǎng)的,如果先不考慮對(duì)方接收數(shù)據(jù)的能力,那么滑動(dòng)窗口的大家就只取決于擁塞窗口的大小,此時(shí)擁塞窗口的大小變化為:1 2 4 8 ……
但我們知道指數(shù)增長(zhǎng)是非??植赖模藭r(shí)就有可能導(dǎo)致網(wǎng)絡(luò)再次擁塞。
此時(shí)就引入了慢啟動(dòng)的閾值,當(dāng)擁塞窗口的大小超過(guò)這個(gè)閾值時(shí),就不再按指數(shù)的方式增長(zhǎng),而按線性的方式增長(zhǎng)。
當(dāng)TCP剛開(kāi)始啟動(dòng)的時(shí)候,慢啟動(dòng)閾值設(shè)置為對(duì)方窗口大小的最大值。
在每次超時(shí)重發(fā)的時(shí)候,慢啟動(dòng)閾值會(huì)變成當(dāng)前擁塞窗口的一半,同時(shí)擁塞窗口的值被重新置為1,如此循環(huán)下去。
如圖:
前期慢開(kāi)始是為了讓網(wǎng)絡(luò)自主恢復(fù),后面指數(shù)增長(zhǎng)是為了盡快恢復(fù)通信。
七、延遲應(yīng)答&捎帶應(yīng)答
- 延遲應(yīng)答
現(xiàn)在接收方緩沖區(qū)有很多數(shù)據(jù),但是應(yīng)用層有很大概率會(huì)馬上把數(shù)據(jù)拿走,如果等一等再應(yīng)答就可以返回更大的窗口。
需要注意的是,延遲應(yīng)答的目的不是為了保證可靠性,而是留出一點(diǎn)時(shí)間讓接收緩沖區(qū)中的數(shù)據(jù)盡可能被上層應(yīng)用層消費(fèi)掉,此時(shí)在進(jìn)行ACK響應(yīng)的時(shí)候報(bào)告的窗口大小就可以更大,從而增大網(wǎng)絡(luò)吞吐量,進(jìn)而提高數(shù)據(jù)的傳輸效率。
此外,不是所有的數(shù)據(jù)包都可以延遲應(yīng)答。
- 數(shù)量限制:每個(gè)N個(gè)包就應(yīng)答一次。
- 時(shí)間限制:超過(guò)最大延遲時(shí)間就應(yīng)答一次(這個(gè)時(shí)間不會(huì)導(dǎo)致誤超時(shí)重傳)。
延遲應(yīng)答具體的數(shù)量和超時(shí)時(shí)間,依操作系統(tǒng)不同也有差異,一般N取2,超時(shí)時(shí)間取200ms。
- 捎帶應(yīng)答
我們知道接收方收到數(shù)據(jù)要給發(fā)送方一個(gè)應(yīng)答,如果剛好接收方也要發(fā)送數(shù)據(jù),是不是可以直接一起返回。
捎帶應(yīng)答最直觀的角度實(shí)際也是發(fā)送數(shù)據(jù)的效率,此時(shí)雙方通信時(shí)就可以不用再發(fā)送單純的確認(rèn)報(bào)文了。
八、TCP衍生問(wèn)題
8.1 面向字節(jié)流
當(dāng)創(chuàng)建一個(gè)TCP的socket時(shí),同時(shí)在內(nèi)核中會(huì)創(chuàng)建一個(gè)發(fā)送緩沖區(qū)和一個(gè)接收緩沖區(qū)。
由于緩沖區(qū)的存在,TCP程序的讀和寫(xiě)不需要一一匹配,例如:
- 寫(xiě)100個(gè)字節(jié)數(shù)據(jù)時(shí),可以調(diào)用一次write寫(xiě)100字節(jié),也可以調(diào)用100次write,每次寫(xiě)一個(gè)字節(jié)。
- 讀100個(gè)字節(jié)數(shù)據(jù)時(shí),也完全不需要考慮寫(xiě)的時(shí)候是怎么寫(xiě)的,既可以一次read100個(gè)字節(jié),也可以一次read一個(gè)字節(jié),重復(fù)100次。
實(shí)際對(duì)于TCP來(lái)說(shuō),它并不關(guān)心發(fā)送緩沖區(qū)當(dāng)中的是什么數(shù)據(jù),在TCP看來(lái)這些只是一個(gè)個(gè)的字節(jié)數(shù)據(jù),它的任務(wù)就是將這些數(shù)據(jù)準(zhǔn)確無(wú)誤的發(fā)送到對(duì)方的接收緩沖區(qū)當(dāng)中就行了,而至于如何解釋這些數(shù)據(jù)完全由上層應(yīng)用來(lái)決定,這就叫做面向字節(jié)流。
這里就可以對(duì)比UDP,UDP不是面向字節(jié)流的,發(fā)一次必須就要讀一次,發(fā)10次就必須讀十次。這種報(bào)文和報(bào)文在傳輸層有明顯邊界的的協(xié)議就叫做面向數(shù)據(jù)報(bào)。
8.2 粘包問(wèn)題
- 什么是粘包?
因?yàn)門(mén)CP是面向字節(jié)流的,所以需要應(yīng)用層來(lái)分開(kāi)這些報(bào)文,如果處理的不好就會(huì)出現(xiàn)多讀了或者少讀了影響到了后續(xù)報(bào)文,這種問(wèn)題就叫做粘包。
- 如何解決粘包問(wèn)題?
解決粘包問(wèn)題的本質(zhì)就是要確定報(bào)文與報(bào)文之間的邊界。
對(duì)于定長(zhǎng)的包,保證每次都按固定大小讀取即可。
對(duì)于變長(zhǎng)的包,可以在報(bào)頭的位置,約定一個(gè)包總長(zhǎng)度的字段,從而就知道了包的結(jié)束位置。比如HTTP報(bào)頭當(dāng)中就包含Content-Length屬性,表示正文的長(zhǎng)度。
對(duì)于變長(zhǎng)的包,還可以在包和包之間使用明確的分隔符。因?yàn)閼?yīng)用層協(xié)議是程序員自己來(lái)定的,只要保證分隔符不和正文沖突即可。
8.3 TCP連接異常
- 進(jìn)程終止
兩個(gè)已經(jīng)建立連接的進(jìn)程,其中一個(gè)進(jìn)程突然掛掉了,此時(shí)建立好的連接會(huì)怎么樣?
其實(shí)連接也是個(gè)文件,而文件描述符是隨進(jìn)程的,進(jìn)程退出,操作系統(tǒng)就會(huì)close掉這個(gè)文件。所以操作系統(tǒng)會(huì)正常四次揮手?jǐn)嚅_(kāi)連接,跟自己掉close沒(méi)區(qū)別。
- 主機(jī)重啟
當(dāng)重啟主機(jī)時(shí),操作系統(tǒng)會(huì)先殺掉所有進(jìn)程然后再進(jìn)行關(guān)機(jī)重啟,因此機(jī)器重啟和進(jìn)程終止的情況是一樣的,此時(shí)雙方操作系統(tǒng)也會(huì)正常完成四次揮手,然后釋放對(duì)應(yīng)的連接資源。
- 拔網(wǎng)線/斷電源
當(dāng)客戶端掉線后,服務(wù)器端在短時(shí)間內(nèi)無(wú)法知道客戶端掉線了,因此在服務(wù)器端會(huì)維持與客戶端建立的連接,但這個(gè)連接也不會(huì)一直維持,因?yàn)門(mén)CP是有?;畈呗缘?。
正常的一方會(huì)不停的詢問(wèn)對(duì)方連接是否還存在,發(fā)現(xiàn)不在了就直接斷開(kāi)。
九、總結(jié)
TCP協(xié)議這么復(fù)雜就是因?yàn)門(mén)CP既要保證可靠性,同時(shí)又盡可能的提高性能。
- 可靠性
檢驗(yàn)和
序列號(hào)
確認(rèn)應(yīng)答
超時(shí)重傳
連接管理
流量控制
擁塞控制
- 提高性能
滑動(dòng)窗口。
快速重傳。
延遲應(yīng)答。
捎帶應(yīng)答。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-581407.html
- 如何用UDP實(shí)現(xiàn)可靠傳輸?
其實(shí)就是參考上面的可靠性。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-581407.html
到了這里,關(guān)于【網(wǎng)絡(luò)編程】傳輸層協(xié)議——TCP協(xié)議的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!