网络编程(五) ———— 万字详解TCP协议
文章目錄
- TCP首部格式
- 1.確認(rèn)應(yīng)答和序列號(hào)
- 2.超時(shí)重傳
- 3.連接管理
- 為啥要三次握手,為啥要建立連接?
- 如果三次握手,握四次行不行,握兩次行不行?
- 如果三次握手,握兩次行不行?
- 四次揮手中的ACK和FIN為啥不合并?
- 4.滑動(dòng)窗口
- 如果滑動(dòng)窗口的場(chǎng)景中出現(xiàn)丟包了,咋辦?
- 快速重傳
- 5.流量控制
- 6.擁塞控制
- 7.延時(shí)應(yīng)答
- 8.捎帶應(yīng)答
- 9.面向字節(jié)流
- 如何解決粘包問(wèn)題?
- 10.TCP中的一些異常情況(心跳機(jī)制)
- 11.如何基于UDP協(xié)議實(shí)現(xiàn)可靠傳輸?
- 啥樣的場(chǎng)景中適合用TCP,啥樣的場(chǎng)景中適合用UDP
TCP首部格式
- 源端口號(hào)
表示發(fā)送端端口號(hào),字段長(zhǎng)16位 - 目標(biāo)端口號(hào)
表示接收端端口號(hào),字段長(zhǎng) 16位 - 序列號(hào)
字段長(zhǎng)32位。序列號(hào)(有時(shí)也就序號(hào))是指發(fā)送數(shù)據(jù)的位置。每次發(fā)送一次數(shù)據(jù),就累加一次該數(shù)據(jù)字節(jié)數(shù)的大小
序列號(hào)不會(huì)從0或1開(kāi)始,而是建立連接時(shí)有計(jì)算機(jī)生成的隨機(jī)數(shù)作為其初始值,通過(guò)SYN包傳給接收端主機(jī)。然后再將每轉(zhuǎn)發(fā)過(guò)去的字節(jié)數(shù)累加到初始值上表示數(shù)據(jù)的位置。此外,在建立連接和斷開(kāi)連接時(shí)發(fā)送的SYN包和FIN包雖然并不攜帶數(shù)據(jù),但是也會(huì)作為一個(gè)字節(jié)增加對(duì)應(yīng)的序列號(hào)。
- 確認(rèn)應(yīng)答號(hào)
確認(rèn)應(yīng)答號(hào)字段長(zhǎng)度32位,是指下一次應(yīng)收到的數(shù)據(jù)的序列號(hào),實(shí)際上,它是指已收到確認(rèn)應(yīng)答號(hào)減一為止的數(shù)據(jù),發(fā)送端收到這個(gè)確認(rèn)應(yīng)答以后可以認(rèn)為在這個(gè)序號(hào)以前的數(shù)據(jù)都已經(jīng)被正常接收
- 數(shù)據(jù)偏移
這個(gè)字段表示所傳輸?shù)臄?shù)據(jù)部分應(yīng)該從TCP包的哪個(gè)位開(kāi)始計(jì)算,也可以把它看作TCP首部的長(zhǎng)度
1.確認(rèn)應(yīng)答和序列號(hào)
確認(rèn)應(yīng)答也是保證可靠性傳輸?shù)暮诵?br /> 發(fā)送方發(fā)數(shù)據(jù)給接收方了,接收方就回應(yīng)一個(gè)應(yīng)答報(bào)文,如果發(fā)送方收到了這個(gè)應(yīng)答報(bào)文,那么認(rèn)為是對(duì)方已經(jīng)收到了。
由于網(wǎng)絡(luò)上的傳輸,順序是不確定的,可能出現(xiàn)后發(fā)先至的情況,因此不能就單純的通過(guò)收到數(shù)據(jù)的順序來(lái)確定邏輯,就需要對(duì)應(yīng)答進(jìn)行的編號(hào)。
實(shí)際上,TCP傳輸數(shù)據(jù),不論條,而是論字節(jié)(面向字節(jié)流)
實(shí)際上,TCP的序號(hào)和確認(rèn)序號(hào),是以字節(jié)為單位進(jìn)行編號(hào)的
比如下面這張圖,第一個(gè)請(qǐng)求A給B發(fā)送了1000個(gè)字節(jié)的數(shù)據(jù),序號(hào)就是1-1000(假設(shè)從1開(kāi)始編號(hào)了),這個(gè)操作相當(dāng)于是發(fā)了一個(gè)TCP數(shù)據(jù)報(bào),這個(gè)數(shù)據(jù)報(bào),這個(gè)數(shù)據(jù)報(bào)的序號(hào)是1,長(zhǎng)度是1000,確認(rèn)應(yīng)答數(shù)據(jù)報(bào),里面的確認(rèn)序號(hào)是1001(意思就是1001之前的數(shù)據(jù),B已經(jīng)收到了,另外,也可以理解成,B在向A索要1001開(kāi)始的數(shù)據(jù))
針對(duì)每個(gè)字節(jié)分別編號(hào)即為序列號(hào),依次進(jìn)行累加(TCP的序號(hào)的起始不一定是從1開(kāi)始的),每個(gè)ACK都帶有對(duì)應(yīng)的確認(rèn)序列號(hào),意思是告訴發(fā)送者,我已經(jīng)收到了哪些數(shù)據(jù),下一次你從哪里開(kāi)始發(fā)
發(fā)送方,就可以根據(jù)確認(rèn)的應(yīng)答報(bào)文來(lái)確定接收方是否是收到了,只要發(fā)送方收到了應(yīng)答,就認(rèn)為接收方已經(jīng)收到,可靠傳輸就完成了。
反之,在一定時(shí)間內(nèi)沒(méi)有等到確認(rèn)應(yīng)答,發(fā)送端就可以認(rèn)為數(shù)據(jù)已經(jīng)丟失,并進(jìn)行重發(fā)。因此,即使產(chǎn)生了丟包,仍然能夠保證數(shù)據(jù)能夠到達(dá)對(duì)端,實(shí)現(xiàn)可靠傳輸
2.超時(shí)重傳
確認(rèn)應(yīng)答機(jī)制中,這個(gè)是比較順利,但是傳輸過(guò)程中還是可能會(huì)出現(xiàn)丟包的,一旦數(shù)據(jù)發(fā)生丟包,就要進(jìn)入超時(shí)重傳機(jī)制中了。
主機(jī)A發(fā)送數(shù)據(jù)給B之后,可能因?yàn)榫W(wǎng)絡(luò)擁堵等原因,數(shù)據(jù)無(wú)法到達(dá)主機(jī)B
如果主機(jī)A在一個(gè)特定時(shí)間間隔內(nèi)沒(méi)有收到B發(fā)來(lái)的確認(rèn)應(yīng)答,就會(huì)進(jìn)行重發(fā)
舉個(gè)列子:
發(fā)送方無(wú)法區(qū)分,當(dāng)前是發(fā)的數(shù)據(jù)丟了,還是應(yīng)答數(shù)據(jù)丟了,發(fā)送方能做的事情,就是在一段時(shí)間之后,重發(fā)一條數(shù)據(jù)
超時(shí)的時(shí)間怎么確定呢?
- 最理想的情況下,找到一個(gè)最小的時(shí)間,保證 “確認(rèn)應(yīng)答一定能在這個(gè)時(shí)間內(nèi)返回”
- 但是這個(gè)時(shí)間的長(zhǎng)短,隨著網(wǎng)絡(luò)環(huán)境的不同,是有差異的 。
- 如果超時(shí)時(shí)間設(shè)的太長(zhǎng),會(huì)影響整體的重傳效率
- 如果超時(shí)時(shí)間設(shè)的太短,有可能會(huì)頻繁發(fā)送重復(fù)的包
TCP為了保證無(wú)論在任何環(huán)境下都能比較高性能的通信,因此會(huì)動(dòng)態(tài)計(jì)算這個(gè)最大超時(shí)時(shí)間
這個(gè)等待,不同的系統(tǒng)實(shí)現(xiàn)的方式不一樣,數(shù)據(jù)在網(wǎng)絡(luò)上傳輸過(guò)程,是需要一定的時(shí)間的,不能說(shuō)數(shù)據(jù)剛發(fā)出去,就期望得到回應(yīng),可能要經(jīng)歷一段時(shí)間之后,才能得到回應(yīng)
Linux中(BSD Unix和Windows也是如此),超時(shí)以500ms為一個(gè)單位進(jìn)行控制,每次判定超時(shí)重發(fā)的超時(shí)時(shí)間都是500ms的整數(shù)倍 ,發(fā)送方,把數(shù)據(jù)發(fā)出去之后,等待500ms,如果沒(méi)有收到應(yīng)答就認(rèn)為是丟包了。
如果重發(fā)一次之后,仍然得不到應(yīng)答,等待 2500ms 后再進(jìn)行重傳。
如果仍然得不到應(yīng)答,等待 4500ms 進(jìn)行重傳。依次類推,以指數(shù)形式遞增
累計(jì)到一定的重傳次數(shù),TCP認(rèn)為網(wǎng)絡(luò)或者對(duì)端主機(jī)出現(xiàn)異常,強(qiáng)制關(guān)閉連接
超時(shí)時(shí)間會(huì)動(dòng)態(tài)變化,不是一成不變的。
等待時(shí)間會(huì)逐漸延長(zhǎng),延長(zhǎng)也就意味著讓重試的頻率盡量降低,只要重傳也失敗,其實(shí)就認(rèn)為,大概率這個(gè)傳輸是通不了的。達(dá)到一定重發(fā)次數(shù)以后,如果仍沒(méi)有任何回應(yīng),就會(huì) 判斷為網(wǎng)絡(luò)或?qū)Χ酥鳈C(jī)發(fā)生了異常,強(qiáng)制關(guān)閉連接,并且通知應(yīng)用通信異常強(qiáng)行終止。
在超時(shí)重傳的時(shí)候,無(wú)法區(qū)分是發(fā)送過(guò)去的數(shù)據(jù)丟了,還是返回的ACK丟了,就一視同仁了,都要進(jìn)行重傳 ,以500ms為單位,依次增加、
如果數(shù)據(jù)重復(fù)了怎么辦?
接收方收到的數(shù)據(jù)會(huì)先放在內(nèi)核的”接收緩沖區(qū)中“,
接收緩沖區(qū)是一段內(nèi)存,每個(gè)socket都有,按照序號(hào)來(lái)進(jìn)行去重,此時(shí)在應(yīng)用程序中讀取的數(shù)據(jù),讀到的結(jié)果就是不帶重復(fù)的
3.連接管理
UDP是一種面向無(wú)連接的通信協(xié)議,因此不檢查對(duì)端是否可以通信,直接將UDP包發(fā)出去,TCP與此相反
TCP是有連接的,連接管理就是,如何建立連接(三次握手),如何斷開(kāi)連接(四次揮手)
-
三次握手本質(zhì)上就是:A向B請(qǐng)求連接,B給與回應(yīng),B也向A請(qǐng)求連接,A也給與回應(yīng)
-
本來(lái)應(yīng)該是“四次握手”,但是中間兩次操作,是可以合在一起的,這兩個(gè)操作在時(shí)間上是同時(shí)發(fā)生的
-
當(dāng)A的SYN到達(dá)B的時(shí)候,B的內(nèi)核就會(huì)第一時(shí)間進(jìn)行應(yīng)答ACK,同時(shí)也會(huì)第一時(shí)間發(fā)起SYN,這兩件事同時(shí)觸發(fā),于是就沒(méi)有必要分成兩次傳輸,直接一步到位
為啥要三次握手,為啥要建立連接?
主要有兩個(gè)目的
如果網(wǎng)絡(luò)出現(xiàn)了問(wèn)題,此時(shí),三次握手都會(huì)難以成功,此時(shí)也就沒(méi)有必要進(jìn)行后續(xù)的傳輸了
如果三次握手,握四次行不行,握兩次行不行?
握四次,完全可以。沒(méi)有必要,效果和3次是一樣的。
中間的ACK和SYN是可以合并在一起的,如果分成兩個(gè),傳輸?shù)拈_(kāi)銷就比一個(gè)大。
如果三次握手,握兩次行不行?
握兩次是肯定不行的,A 給 B發(fā)送一個(gè) SYN,B再給A同時(shí)發(fā)送一個(gè)ACK和SYN就完成了兩次握手。
此時(shí)A知道自己發(fā)送和接收能力沒(méi)有問(wèn)題,也知道B的發(fā)送和接收能力沒(méi)問(wèn)題
但是B只知道自己的接收能力沒(méi)問(wèn)題,但不知道自己的發(fā)送能力有沒(méi)有問(wèn)題
B只知道A的發(fā)送能力沒(méi)有問(wèn)題,并不知道A的接收能力有沒(méi)有問(wèn)題。所以并不能進(jìn)行傳輸。
服務(wù)器和客戶端
四次揮手中的ACK和FIN為啥不合并?
對(duì)于B來(lái)說(shuō) ACK 和 FIN 的觸發(fā)時(shí)機(jī)是不一樣的。
- CLOSE_WAIT:服務(wù)器收到FIN之后,進(jìn)入的狀態(tài),等待用戶代碼調(diào)用close,來(lái)發(fā)送FIN
- TIME_WAIT:表示的客戶端收到了FIN之后進(jìn)入了TIME_WAIT,這個(gè)狀態(tài)存在的意義主要就是為了處理最后一個(gè)ACK包
在三次握手和四次揮手的過(guò)程中,同樣可能會(huì)丟包,一旦丟包就會(huì)觸發(fā)超時(shí)重傳
假設(shè),如果A收到FIN,并返回ACK之后,連接就銷毀,而不是進(jìn)入TIME_WAIT狀態(tài),會(huì)咋樣?
此時(shí)一旦最后一個(gè)ACK丟了,此時(shí)就無(wú)法重傳ACK了(連接已經(jīng)銷毀了)
TINE_WAIT即使進(jìn)程已經(jīng)退出了,TIME_WAIT狀態(tài)仍然會(huì)存在(TCP連接不會(huì)立即銷毀),TIME_WAIT 會(huì)等待一定時(shí)間,如果一定時(shí)間之內(nèi)也沒(méi)有重傳的FIN過(guò)來(lái),才會(huì)真正銷毀
這個(gè)等待時(shí)間為2*MSI
MSI理論上是,主機(jī)A和B之間最長(zhǎng)的一次通信時(shí)間
通常在Linux中這個(gè)MSL默認(rèn)是1min,這個(gè)MSL默認(rèn)是1min,當(dāng)然這個(gè)MSL都是可以配置的
如果服務(wù)器上出現(xiàn)大量的CLOSE_WAIT,是啥情況?
這是代碼出現(xiàn)bug,close,沒(méi)有及時(shí)被調(diào)用到,
如果服務(wù)器上出現(xiàn)大量的TIME_WAIT,是啥情況?
主動(dòng)發(fā)起FIN的一方會(huì)進(jìn)入TIME_WAIT,就需要排查服務(wù)器是否應(yīng)該主動(dòng)斷開(kāi)連接
這個(gè)也可能是代碼bug,但是不能石錘
哪方先斷開(kāi)連接,哪方就會(huì)進(jìn)入TIME_WAIT,進(jìn)程退出之后,TIME_WAIT狀態(tài)仍然存在,TCP連接仍然存在
如果讓服務(wù)器先退出,服務(wù)器這邊就會(huì)進(jìn)入到 TIME_WAIT狀態(tài)(原來(lái)的連接占據(jù)著端口),接下來(lái)如果服務(wù)器立即啟動(dòng),新的進(jìn)程又會(huì)嘗試重新綁定這個(gè)端口可能
會(huì)存在端口綁定失敗的情況,如果使用原生的socket來(lái)試效果會(huì)非常明顯,第二次啟動(dòng)就會(huì)啟動(dòng)失敗。
在Java socket,一般來(lái)說(shuō)第二次啟動(dòng)也是能成功的
在socket api 里有一個(gè) REUSE ADDR 選項(xiàng),如果把這個(gè)選項(xiàng)加上,就能夠讓我們綁定端口的時(shí)候復(fù)用TIME_WAIT狀態(tài)中的端口,(Java socket里面應(yīng)該是默認(rèn)就設(shè)置了這個(gè)選項(xiàng))
- LISTEN:手機(jī)開(kāi)機(jī) ,信號(hào)良好,隨時(shí)可以打入電話,服務(wù)器的狀態(tài),當(dāng)我們創(chuàng)建好ServerSocket實(shí)例的時(shí)候,就進(jìn)入了LISTEN狀態(tài)
- ESTABLISHED:接通了電話,雙方可說(shuō)話了,代碼中accept放回了,得到了一個(gè)clientSocke
- stable:穩(wěn)定的
四次揮手一定是四次嘛?是否可能是三次呢?
有可能的,后面的:延時(shí)應(yīng)答和捎帶應(yīng)答雖然ACK和FIN是不同時(shí)機(jī),但是在延時(shí)應(yīng)答和捎帶應(yīng)答的情況下是可能合并在一起的
四次揮手一定會(huì)執(zhí)行嘛?也不一定
四次揮手是一個(gè)TCP正常斷開(kāi)的流程,但是實(shí)際上,有的時(shí)候TCP連接也會(huì)異常斷開(kāi)(比如網(wǎng)斷了)
4.滑動(dòng)窗口
TCP不僅僅是為了保證可靠性,還要盡可能的提高傳輸效率。
其實(shí)可靠性和效率,是矛盾的。TCP努力的在可靠性的前提下,又做出了很多性能優(yōu)化的手段
下圖的這個(gè)發(fā)送過(guò)程,發(fā)送方需要花很多時(shí)間來(lái)等,這個(gè)等待其實(shí)就浪費(fèi)了大量時(shí)間
現(xiàn)在通過(guò)批量發(fā)送,一次發(fā)送一波,一次等一波的ACK,把多組數(shù)據(jù)的ACK的等待時(shí)間給重疊起來(lái)了。
一次批量發(fā)的數(shù)據(jù)的長(zhǎng)度,就稱為“窗口大小”
如果沒(méi)有批量發(fā)送數(shù)據(jù)的長(zhǎng)度限制(窗口無(wú)限大,完全不等,ACK就一頓發(fā)),其實(shí)就沒(méi)有可靠性而言
如果窗口越大,其實(shí)整體的效率就越高
如果窗口越小,整體的效率就越低
當(dāng)前窗口范圍是1001 - 5000,也就意味著,發(fā)送方現(xiàn)在同時(shí)發(fā)送了(1001 - 2000;2001-3000;3001-4000;4001-5000),同時(shí)再等待著四組數(shù)據(jù)的ACK
假設(shè),2001這個(gè)ACK先到,發(fā)送方就知道了1001-2000這個(gè)數(shù)據(jù)已經(jīng)被對(duì)方收到了
發(fā)送方也就不用繼續(xù)等這個(gè)數(shù)據(jù)了,接下來(lái)就立即再發(fā)一個(gè)5001 - 6000,仍然保證窗口大小時(shí)4份數(shù)據(jù),仍然保證當(dāng)前同時(shí)等待4份數(shù)據(jù)的ack
并不是把4份ack都等到,才發(fā)新的數(shù)據(jù),而是隨著收到ack,就隨著往后發(fā)送。
假設(shè)有后發(fā)先至的情況
ack 2001,3001,4001,5001 都在網(wǎng)絡(luò)上傳輸呢,不一定非得是2001先到,是否可能3001先到呢?
這是非常有可能的。
確認(rèn)序號(hào)表示,從該序號(hào)之前,前面的數(shù)據(jù)都收到了
如果收到了3001這個(gè)ack,意思就是 1001-2000 和 2001-3000都被對(duì)方收到了,此時(shí)2001這個(gè)ack收或者不收,已經(jīng)不關(guān)鍵。
如果滑動(dòng)窗口的場(chǎng)景中出現(xiàn)丟包了,咋辦?
情況1:數(shù)據(jù)包已經(jīng)抵達(dá),ACK丟失了
這種情況沒(méi)有關(guān)系,只要不是全部ACK丟失就好了,哪怕丟個(gè)50%也沒(méi)事
發(fā)送方:1001-2000,2001-3000
接收方:2001,3001
如果2001這個(gè)acck丟了,3001這個(gè)ack到了
此時(shí)發(fā)送方也就知道了,3001前面的數(shù)據(jù)都被正確收到了
此時(shí)1001-2000這個(gè)數(shù)據(jù)報(bào)也得到了一個(gè)確認(rèn)應(yīng)答
實(shí)際上,TCP為了偷懶(提高效率),滑動(dòng)窗口下,并不是每一條數(shù)據(jù)都有ACK,會(huì)隔幾條數(shù)據(jù)才有一個(gè)ACK
快速重傳
快速重傳,效率很高,尤其是不需要重復(fù)傳輸數(shù)據(jù)
如上圖,因?yàn)?001丟包了,主機(jī)B就會(huì)一直索要1001,確認(rèn)序號(hào)就仍然是1001
1001-2000這個(gè)數(shù)據(jù)丟了,2001-3000,3001-4000…還在繼續(xù)發(fā)
發(fā)送方這邊,如果連續(xù)看到幾次1001這個(gè)ACK,就知道了是1001這個(gè)數(shù)據(jù)丟失了,接下來(lái)就會(huì)重傳1001
前面的2001-7000這些數(shù)據(jù)已經(jīng)到達(dá)了接收端了,值不過(guò)是在接收緩沖區(qū)里待著,當(dāng)1001-2000這個(gè)數(shù)據(jù)到達(dá)的時(shí)候,B就知道了,7001之前的數(shù)據(jù)就都到齊了,此時(shí)就繼續(xù)索要7001這個(gè)數(shù)據(jù)即可
5.流量控制
流量控制,本質(zhì)上就是在控制滑動(dòng)窗口的大小,也是保證可靠性的。
窗口大小決定了傳輸?shù)男?#xff0c;窗口越大,效率就越高,窗口越小,效率就越低。
既然如此,窗口大小取多少合適呢?
窗口越大,為了保證可靠性,資源開(kāi)銷就得越多
窗口太小,速度也得不到保證
流量控制是基于接收方的處理能力來(lái)限制窗口大小的
TCP這個(gè)傳輸數(shù)據(jù)的過(guò)程,其實(shí)也就類似于一個(gè)生產(chǎn)者消費(fèi)者模型
主機(jī)A發(fā)送的數(shù)據(jù)就到達(dá)了主機(jī)B的接收緩沖區(qū),此時(shí)主機(jī)A就是生產(chǎn)者,主機(jī)B的應(yīng)用程序,通過(guò)socket api 來(lái)讀取數(shù)據(jù)。被soket api 讀到的數(shù)據(jù)就從緩沖區(qū)中刪掉了,應(yīng)用程序就是消費(fèi)者,接收緩沖區(qū)就是交易場(chǎng)所(類似于一個(gè)隊(duì)列)
所說(shuō)的窗口大小,是指發(fā)送發(fā)(主機(jī)A)批量發(fā)多少數(shù)據(jù),比如,主機(jī)A發(fā)的數(shù)據(jù)很快,窗口很大,此時(shí)接收緩沖區(qū)的數(shù)據(jù)也會(huì)增長(zhǎng)很快,如果主機(jī)B的應(yīng)用程序讀取數(shù)據(jù)讀的不快,隨著時(shí)間的推移,接收緩沖區(qū)逐漸就滿了,如果不加任何限制,主機(jī)A還是按照一樣的速度發(fā),此時(shí)新來(lái)的數(shù)據(jù)沒(méi)有地方保存,就被內(nèi)核丟了。
類似于一個(gè)水池,一邊注水,一邊出水,如果注水速度比出水大,池子很快就滿了
流量控制這個(gè)機(jī)制,就是為了解決這個(gè)問(wèn)題的,根據(jù)接收方的處理能力(接收緩沖區(qū)的剩余空間大小),來(lái)動(dòng)態(tài)決定發(fā)送方的發(fā)送速率(控制窗大小)
接收緩沖區(qū)大小是4000
1-1000數(shù)據(jù)到達(dá)的時(shí)候,緩沖區(qū)這里面用了1000,還剩3000,返回的ack中就會(huì)把3000這個(gè)信息告訴發(fā)送方
發(fā)送方再次發(fā)送數(shù)據(jù)的時(shí)候,就按照3000作為窗口大小來(lái)進(jìn)行發(fā)送
窗口大小(接收緩沖區(qū)的剩余空間)是如何放回給發(fā)送方的?
如果窗口大小為0 了(接收端這邊滿了),然后發(fā)送方就停了嗎?
此時(shí)發(fā)送方式不再繼續(xù)發(fā)數(shù)據(jù)了,但是為了能夠查詢當(dāng)前接收方的窗口大小。每隔一段時(shí)間,還會(huì)再來(lái)觸發(fā)一個(gè)窗口探測(cè)包,通過(guò)這個(gè)包(不傳輸具體的業(yè)務(wù)數(shù)據(jù)),觸發(fā)ACK,在這個(gè)ACK中就能知道當(dāng)前窗口的大小了
那么問(wèn)題來(lái)了,16位數(shù)字最大表示65535,那么TCP窗口最大就是65535字節(jié)么?
64K窗口大小夠嗎?
TCP首部40字節(jié)選項(xiàng)中還包含了一個(gè)窗口擴(kuò)大因子,實(shí)際窗口大小是 窗口字段的值 左移M位(左移以為相當(dāng)于*2)
6.擁塞控制
擁塞控制是站在另外一個(gè)角度來(lái)限制發(fā)送方的窗口大小,站在一個(gè)宏觀角度來(lái)看待這個(gè)問(wèn)題,把整個(gè)中間鏈路都看成了一個(gè)整體,只看結(jié)果。
先使用一個(gè)比較小的窗口來(lái)傳輸數(shù)據(jù),看看是否丟包,
如果不丟包,說(shuō)明網(wǎng)絡(luò)比較通暢,如果丟包,說(shuō)明網(wǎng)絡(luò)發(fā)送擁堵
當(dāng)網(wǎng)絡(luò)通暢的時(shí)候,就逐漸加大發(fā)送速率。當(dāng)網(wǎng)絡(luò)出現(xiàn)丟包的時(shí)候,就立即降低發(fā)送速率。
通過(guò)這樣的方式,就可以逐漸實(shí)驗(yàn)出一個(gè)比較合適的窗口大小
真實(shí)的發(fā)送窗口大小 = min(流量控制的窗口,擁塞控制的窗口)
慢啟動(dòng):剛開(kāi)始啟動(dòng)的時(shí)候,給一個(gè)較小的窗口(比較慢的發(fā)送速率)
這個(gè)圖描述了擁塞控制中,窗口大小的變化規(guī)則
指數(shù)增長(zhǎng)速度是非??斓?#xff0c;由于剛開(kāi)始啟動(dòng)的時(shí)候,窗口比較小,此時(shí)通過(guò)指數(shù)增長(zhǎng),就能在很少的輪次中就能把窗口大小給頂上去
如果達(dá)到閾值就從指數(shù)增長(zhǎng)變成線性增長(zhǎng)
7.延時(shí)應(yīng)答
延時(shí)應(yīng)答也是用來(lái)提高效率的(琢磨窗口大小)
讓窗口大小,在保證可靠的基礎(chǔ)之上,能盡量再大一點(diǎn)
流量控制來(lái)說(shuō),窗口大小就是接收緩沖區(qū)的剩余空間大小
主機(jī)A給主機(jī)B發(fā)送數(shù)據(jù),如果接收方立刻放回ACK,此時(shí)放回的窗口大小,就是當(dāng)下這個(gè)緩沖區(qū)剩余的空間。
但是如果接收方稍等一會(huì),再返回ACK,稍等這個(gè)時(shí)間里,應(yīng)用程序可能就會(huì)消費(fèi)一部分?jǐn)?shù)據(jù),此時(shí)緩沖區(qū)的剩余空間就更大了
8.捎帶應(yīng)答
在延時(shí)應(yīng)答的基礎(chǔ)之上
很多的客戶端/服務(wù)器的通信服務(wù)器的通信模式,都是這種“一問(wèn)一答”
但是由于有了延時(shí)應(yīng)答,返回的ACK不是立即返回,而是等一會(huì)。
正好等一會(huì)之后,服務(wù)器要返回業(yè)務(wù)上的response了,此時(shí)就可以把這個(gè)ACK和response合二為一,把兩個(gè)包變成一個(gè)包
網(wǎng)絡(luò)通信涉及到大量的封裝和分用,針對(duì)每個(gè)包都要一頓封裝,收到之后再一頓解析。
針對(duì)四次揮手來(lái)說(shuō),確實(shí)是可能四次變成三次的
捎帶應(yīng)答,是可能吧中間的ACK和FIN給合并成一個(gè),于是四次揮手就變成了三次揮手。
四次揮手啥時(shí)候能變成三次?這是不能確定的。
捎帶應(yīng)答本身就是一個(gè)“概率性的機(jī)制”,當(dāng)前ACK延時(shí)的時(shí)間正好要比接下來(lái)發(fā)業(yè)務(wù)數(shù)據(jù)的時(shí)間要更長(zhǎng)一些。
列如,服務(wù)器收到請(qǐng)求到返回響應(yīng),這個(gè)過(guò)程消耗時(shí)間50ms
但是延時(shí)應(yīng)答假設(shè)最多等20ms,這個(gè)情況就無(wú)法觸發(fā)捎帶應(yīng)答了
但是延時(shí)應(yīng)答假設(shè)是最多等60ms
第50ms的時(shí)候,此時(shí)觸發(fā)了響應(yīng),ACK就可以和這個(gè)響應(yīng)一起過(guò)去了,也就是觸發(fā)了延時(shí)應(yīng)答
9.面向字節(jié)流
在這種面向字節(jié)流的情況下,需要注意一個(gè)重要的問(wèn)題:粘包問(wèn)題(指的是應(yīng)用層的數(shù)據(jù)報(bào))
應(yīng)用程序從接收緩沖區(qū)讀數(shù)據(jù)的時(shí)候,就不知道從哪里到哪里是一個(gè)完整的應(yīng)用層數(shù)據(jù)報(bào)
應(yīng)用程序此時(shí)只能看到接收緩沖區(qū)中的一個(gè)一個(gè)字節(jié),無(wú)法區(qū)分當(dāng)前接收緩沖區(qū)里有多少個(gè)應(yīng)用層數(shù)據(jù)報(bào),以及從哪到哪是一個(gè)完整的應(yīng)用層數(shù)據(jù)報(bào)
如何解決粘包問(wèn)題?
通過(guò)設(shè)計(jì)一個(gè)合理的應(yīng)用層協(xié)議來(lái)解決
方式一:設(shè)定結(jié)束符,約定每個(gè)應(yīng)用層數(shù)據(jù)報(bào)一定以 ; 結(jié)尾
方式二:設(shè)定包的長(zhǎng)度,約定每個(gè)應(yīng)用層數(shù)據(jù)報(bào)的前4個(gè)字節(jié),存儲(chǔ)數(shù)據(jù)報(bào)的長(zhǎng)度
在UDP中是不存在粘包問(wèn)題的
10.TCP中的一些異常情況(心跳機(jī)制)
常見(jiàn)的異常情況
進(jìn)程終止
不管進(jìn)程是咋終止的,本質(zhì)上都會(huì)釋放對(duì)應(yīng)的PCB,也會(huì)釋放對(duì)應(yīng)的文件描述符,一樣會(huì)觸發(fā) 四次揮手
"進(jìn)程終止"不代表連接就終止,進(jìn)程終止其實(shí)就相當(dāng)于調(diào)用了 soket.close() 方法而已
機(jī)器重啟
機(jī)器重啟的時(shí)候,其實(shí)也是先殺進(jìn)程,仍然是進(jìn)行四次揮手
機(jī)器掉電、網(wǎng)線斷開(kāi)
突發(fā)情況,機(jī)器來(lái)不及進(jìn)行任何動(dòng)作的
如果掉電的是接收方,
此時(shí)另外一邊還在發(fā)送數(shù)據(jù),此時(shí)顯然發(fā)送方不會(huì)再有ACK,于是就會(huì)超時(shí)重傳.
重傳幾次之后,就會(huì)嘗試重置連接,這個(gè)時(shí)候,RST(復(fù)位報(bào)文段)就會(huì)設(shè)置為1
再然后發(fā)送方就會(huì)放棄這個(gè)連接,把連接對(duì)應(yīng)的資源就回收了。
如果掉電的是發(fā)送方(心跳機(jī)制)
此時(shí)另外一方在嘗試接收數(shù)據(jù),此時(shí)接收不到任何數(shù)據(jù)
接收方如何知道,發(fā)送方式掛了?還是說(shuō)發(fā)送方暫時(shí)還沒(méi)發(fā)呢?
此時(shí)接收方采取的策略,就是"心跳包"機(jī)制(也叫做“?;睢?
每隔一段時(shí)間,向?qū)Ψ桨l(fā)送一個(gè) PING包,期待對(duì)方返回一個(gè)PONG包。
如果PING包發(fā)故去,過(guò)了很久還沒(méi)有PONG,并且重試幾次也不行,此時(shí)就認(rèn)為對(duì)方已經(jīng)掛了
心跳包是一個(gè)應(yīng)用非常廣泛的機(jī)制,不僅僅是在TCP
在微服務(wù)中,如果某個(gè)主機(jī)宕機(jī)了,此時(shí)入口服務(wù)器就得即使發(fā)現(xiàn)這個(gè)事情,就需要把請(qǐng)求切走
就可以使用心跳包機(jī)制,直接使用一個(gè)TCP連接時(shí)不行的,雖然使用TCP連接能夠感知是哪個(gè)主機(jī)掛了,但是TCP感知的不夠及時(shí),如果希望能夠更加及時(shí)更快速的發(fā)現(xiàn)問(wèn)題
就需要在應(yīng)用層實(shí)現(xiàn)心跳機(jī)制
11.如何基于UDP協(xié)議實(shí)現(xiàn)可靠傳輸?
這個(gè)問(wèn)題其實(shí)是在考TCP
啥樣的場(chǎng)景中適合用TCP,啥樣的場(chǎng)景中適合用UDP
如果需要可靠傳輸,肯定首選TCP
如果傳輸單個(gè)數(shù)據(jù)報(bào)比較長(zhǎng)(超過(guò)64K),還是首選TCP
如果特別注重效率,優(yōu)先考慮UDP
典型的場(chǎng)景:機(jī)房?jī)?nèi)部的主機(jī)通信
網(wǎng)絡(luò)環(huán)境簡(jiǎn)單,帶寬充裕,丟包的概率不大
機(jī)房?jī)?nèi)部主機(jī)之間的通信,往往傳輸數(shù)據(jù)量更大,更需要速度
尤其是在當(dāng)下的"微服務(wù)"這樣的環(huán)境中,其實(shí)特別需要
如果需要廣播,優(yōu)先考慮UDP
一份數(shù)據(jù)同時(shí)發(fā)給多個(gè)主機(jī)
UDP自身就支持廣播的
但是TPC自身不支持廣播,就只能在應(yīng)用程序中,通過(guò)多個(gè)連接,輪詢的方式給每個(gè)主機(jī)發(fā)送數(shù)據(jù)(偽廣播)
除了TPC和UDP之外還有很多其它協(xié)議,有的協(xié)議就可以盡可能的兼顧到可靠性和效率(兼顧可靠性和效率,付出的代價(jià)可能激素會(huì)更多的機(jī)器資源)
總結(jié)
以上是生活随笔為你收集整理的网络编程(五) ———— 万字详解TCP协议的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 通过调用rundll32.exe来打开一
- 下一篇: 10106