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