?個人主頁:bit me??
?當前專欄:Java EE初階??
??一. 確認應答
TCP 誕生的初衷就是可靠傳輸
可靠傳輸是 TCP 最核心的部分,TCP 內(nèi)部的很多機制,都是在保證可靠傳輸。(可靠傳輸是發(fā)了之后我知道對方收沒收到,而不是 100% 能收到(可能沒收到 --> 網(wǎng)線斷了))
可靠性如何保證的呢?確認應答 ?。?br> ?
此處說的 “可靠性” 和 “安全性” 沒有任何關(guān)系,千萬不能說 TCP 這個可靠的通信協(xié)議,說成 “安全的” 通信協(xié)議。安全性:指的是,數(shù)據(jù)被截獲之后,不容易被理解內(nèi)部的意思或者被篡改,通過加密來做到的。
確認應答,要針對數(shù)據(jù)進行編號,然后才能明確,應答報文是在應答哪個數(shù)據(jù),應對了網(wǎng)絡(luò)傳輸?shù)?“后發(fā)先至”。
TCP 就引入了 “序號”
32 位序號給發(fā)送的每個數(shù)據(jù),都進行編號了,32 位確認序號表示:如果當前報文是一個普通的報文,確認序號不生效;如果當前報文是應答報文,確認序號就表示應答的是哪個普通報文。
報文的字節(jié):
由于 TCP 是面向字節(jié)流的,編號的時候,也不是說按照 “條” 的方式編的,而是按照字節(jié)來編。
這一口氣發(fā)了 1000 個字節(jié)的數(shù)據(jù)(一個 TCP 的數(shù)據(jù)報,長度是 1000,序號是 1),TCP 報頭中的序號是 1 ,整個報文長度是 1000,就相當于把序號 1 - 1000 的這些字節(jié)都發(fā)過來了。
應答報文中的確認序號,就是 1001。應答報文,可以視為只有 TCP 報頭,沒有載荷,在這個報頭里,確認序號字段,填寫了 1001 ,意思就是 < 1001 的數(shù)據(jù),B 已經(jīng)收到了,接下來 A 就要從 1001 開始往后發(fā)送?。?!
此處有人會問,TCP 不是傳輸?shù)淖止?jié)流嗎?怎么傳輸?shù)臄?shù)據(jù)報?
?
例如在我們的 UDP 中:
B 這邊調(diào)用一個 recv 方法:
- 第一次調(diào)用讀取的肯定是 1111
- 第二次調(diào)用讀取的肯定是 2222
- 第三次調(diào)用讀取的肯定是 3333
?
每次調(diào)用 recv 方法就是從接收緩沖區(qū)里取走一個數(shù)據(jù),UDP 的接收緩沖區(qū),相當于一個鏈表,里面有三個節(jié)點,每次讀,都以一個節(jié)點為單位
?
這就是面向數(shù)據(jù)報
在 TCP 中:
用 InputStream.read(buffer)
- byte[1] buffer 讀出來的就是 1
- byte[3] buffer 讀出來的就是 111
- byte[5] buffer 讀出來的就是 11112
- byte[7] buffer 讀出來的就是 1111222
?
TCP 的接收緩沖區(qū),更像是一個數(shù)組,若干個 TCP 數(shù)據(jù)報的載荷會一直追加到這個數(shù)組里,融為一體。
?
這就是面向字節(jié)流
面向字節(jié)流和面向數(shù)據(jù)報,主要是影響代碼咋寫(影響著應用層!!!),而在傳輸層這里,仍然是一個一個報文來傳輸數(shù)據(jù)的。(傳輸了報文,不意味著 “面向數(shù)據(jù)報”,兩個不同的概念)
如何區(qū)分,一個報文是普通報文還是應答報文呢?
在 TCP 報頭里有六個非常重要的 bit 位,其中第二位 ACK 就表示是否是應答報文
- ACK 為 0 表示不是應答報文??!
- ACK 為 1 表示是應答報文?。?!
這六個 bit 位的各個含義:
- URG:緊急指針是否有效
- ACK:確認號是否有效
- PSH:提示接收端應用程序立刻從TCP緩沖區(qū)把數(shù)據(jù)讀走
- RST:對方要求重新建立連接;我們把攜帶RST標識的稱為復位報文段
- SYN:請求建立連接;我們把攜帶SYN標識的稱為同步報文段
- FIN:通知對方,本端要關(guān)閉了,我們稱攜帶FIN標識的為結(jié)束報文段
在確認應答的情況下,如果收到了 ACK 就好辦,如果沒收到呢?還需要通過其他途徑來處理!
??二. 超時重傳
ACK 沒有收到的時候,不應立即放棄,需要重新再發(fā)一遍。
網(wǎng)絡(luò)的環(huán)境是非常復雜的,尤其是有時候網(wǎng)絡(luò)會擁堵,擁堵就可能導致丟包
丟包是 “無差別” 的,任何一個數(shù)據(jù)報,都有可能會丟包。發(fā)送的普通的報文是可能丟的;發(fā)送的 ACK 也是可能丟的。
業(yè)務(wù)數(shù)據(jù)丟了:
ACK 丟了:
業(yè)務(wù)數(shù)據(jù)已經(jīng)到了主機 B 了,反饋的 ACK 沒有回過去,發(fā)送方等待了一會兒之后,就觸發(fā)了重傳。對于發(fā)送方來說,無法區(qū)分是業(yè)務(wù)數(shù)據(jù)丟了還是 ACK 丟了,因此發(fā)送方能做的就是達到一定時間之后,就重傳。
這個情況下,主機 B 收到了兩份 1 - 1000 這個數(shù)據(jù)!此時,如果在應用層調(diào)用 read ,讀出來的是 1 份數(shù)據(jù)好,還是讀出兩份一樣的數(shù)據(jù)好?
?
當然是一份數(shù)據(jù)好,畢竟兩份數(shù)據(jù)不是咱想要的。
那么 TCP 協(xié)議需要能夠識別出那些包是重復的包,并且把重復的丟棄掉。這時候我們可以利用前面提到的序列號,就可以很容易做到去重的效果。B 知道自己都收到了哪些數(shù)據(jù),當發(fā)現(xiàn)又收到了一份之前的數(shù)據(jù),就會自動丟棄,保證應用層讀到的數(shù)據(jù)是不重復的。
但是在數(shù)據(jù)轉(zhuǎn)發(fā)過程中,可能會發(fā)生很多種可能:
- 本來是 1 - 1000 -> 1 - 500
- 本來是 1 - 1000 -> 1 - 2000
- 本來是 1 - 1000 -> 1 - 1000(內(nèi)容變了)
數(shù)據(jù)發(fā)送出去之后,就會同時發(fā)送數(shù)據(jù)內(nèi)容 + 校驗和,數(shù)據(jù)接收方會按照同樣的規(guī)則,再算一次校驗和,并且和收到的校驗和作比較。在任意傳輸過程中,某個中間節(jié)點,發(fā)現(xiàn)校驗和不對,都會主動觸發(fā)丟包。
內(nèi)容變了 / 長度變了 校驗和也就變了,就可以通過校驗和知道這里的變化,發(fā)現(xiàn)數(shù)據(jù)有問題就丟棄!
超時重傳機制下,發(fā)一個數(shù)據(jù),丟包了,重傳數(shù)據(jù),是否還會丟包呢?當然會!比如我們概率來算,丟包幾率為 10% ,連續(xù)兩次丟包概率就是 10% * 10% = 1%。
丟包操作,還有一個超時時間,超時時間具體是多少,在操作系統(tǒng)內(nèi)核是可以配置的。
- 第一次丟包,超時時間是 T1
- 第二次丟包,超時時間是 T2
?
T2 > T1 這里等待的時間間隔隨著時間的推移越來越大,連續(xù)兩次沒發(fā)過去,意味著當前單次丟包的概率已經(jīng)相當大了,很可能是網(wǎng)絡(luò)上遇到了非常嚴重的事故,短期內(nèi)恢復不了,發(fā)送的再頻繁也沒用。超時重傳也不會無限制的重傳下去,嘗試幾次之后,仍然無法傳送過去,此時就會放棄重傳,然后嘗試斷開重連,如果重連還沒連上去,就徹底放棄了。
超時的時間如何確定?
- 最理想的情況下,找到一個最小的時間,保證 “確認應答一定能在這個時間內(nèi)返回”。
- 但是這個時間的長短,隨著網(wǎng)絡(luò)環(huán)境的不同,是有差異的。
- 如果超時時間設(shè)的太長,會影響整體的重傳效率;
- 如果超時時間設(shè)的太短,有可能會頻繁發(fā)送重復的包;
TCP為了保證無論在任何環(huán)境下都能比較高性能的通信,因此會動態(tài)計算這個最大超時時間。
- Linux中(BSD Unix和Windows也是如此),超時以500ms為一個單位進行控制,每次判定超時重發(fā)的超時時間都是500ms的整數(shù)倍。
- 如果重發(fā)一次之后,仍然得不到應答,等待 2*500ms 后再進行重傳。
- 如果仍然得不到應答,等待 4*500ms 進行重傳。依次類推,以指數(shù)形式遞增。
- 累計到一定的重傳次數(shù),TCP認為網(wǎng)絡(luò)或者對端主機出現(xiàn)異常,強制關(guān)閉連接。
以上兩種機制可以認為是保證 TCP 可靠性的,最核心的機制
??三. 連接管理
- 網(wǎng)絡(luò)知識中,最高頻的考點,沒有之一?。?!
?- 談到操作系統(tǒng),最高頻的考題是進程和線程的區(qū)別
- 談到網(wǎng)絡(luò),最高頻的考點是 TCP 的三次握手四次揮手
- 談到數(shù)據(jù)結(jié)構(gòu),最高頻的考點是哈希表
在正常情況下,TCP要經(jīng)過三次握手建立連接,四次揮手斷開連接
主動的那一方是客戶端,三次握手,一定是客戶端先發(fā)起的
中間有兩步,不過都是從服務(wù)器到客戶端,所以合并為一次。經(jīng)歷了這四次交互,就完成了建立連接的過程。兩對操作,客戶端和服務(wù)器,相互給對方發(fā)送了一個 SYN ,再互相給對方發(fā)送了一個 ACK ,中間的兩次合并為一次,所以稱為 “三次握手”。
為啥要和并為一次傳輸?每次報文傳輸,都要經(jīng)過一系列的封裝分用!分成兩個包來發(fā)就比一個包代價大很多。
由我們的標志位可知,第五個標志位 SYN 是同步報文,第二個標志位 ACK 是確認報文,所以合并的那個報文第二個和第五個報文都是 1 。
- 為啥要三次握手?(意義 / 初心)
?
- 三次握手,認為是一種保證可靠性的機制!這個東西相當于 “投石問路” ,在正式通信之前,先確認好通信鏈路是否通暢!如果通信鏈路不通暢,后續(xù)大概率要丟包!
- 讓通信雙方協(xié)商一些重要的參數(shù)(比如一次連接中不一定是從序號 1 開始的。也例如結(jié)婚)
![]()
?
2. 為啥三次握手要握三次呢?四次行不行?兩次行不行?
?
四次可以,但是沒必要,因為效率低;兩次鐵定不行,三次握手也是在驗證通信雙方的發(fā)送能力和接收能力是否正常!
四次揮手:斷開連接的過程。(客戶端和服務(wù)器都可以主動?。?/font>
和三次握手不同,四次揮手看起來也是雙方各自給對方發(fā)送 FIN ,各自給對方發(fā)送 ACK,這里是四次揮手,中間兩次不一定能合并!!
B 返回 ACK 是操作系統(tǒng)內(nèi)核行為,操作系統(tǒng)內(nèi)核收到 FIN 之后,就會立即返回 ACK ,而接下來 B 的 FIN 是用戶代碼的行為,用戶在代碼中調(diào)用 socket.close 方法,才會觸發(fā) FIN 。
因此,B 發(fā)送 FIN 和 發(fā)送 ACK 之間會有不可忽略的時間間隔!正因為有了時間間隔,就不能合并?。ǖ?TCP 中還有 延時應答和捎帶應答 會可能造成合并,后文詳解)
?
圖中有三部分信息:
- 三次握手四次揮手中間數(shù)據(jù)傳輸流程
- 三次握手四次揮手過程中 TCP 狀態(tài)轉(zhuǎn)換
- 每個環(huán)節(jié)涉及到的 socket.api
?
TCP 的狀態(tài)比多線程還多很多:(無需都背下來)
- LISTEN 服務(wù)器的狀態(tài)
服務(wù)器已經(jīng)啟動完畢,已經(jīng)綁定端口成功?。ㄊ謾C開機完畢,信號良好,隨時可以給我打電話)
- ESTABLISHED
連接建立好了之后,可以進行后續(xù)通信,打電話號碼撥通,對方已經(jīng)接聽了,接下來就可以隨時說話了
- CLOSE_WAIT
被動接受 FIN 的一方,進入 CLOSE_WAIT ,這個狀態(tài)就是自己收到了 FIN 也返回了 ACK ,在自己發(fā)送 FIN 之前處在的狀態(tài)。調(diào)用 close 方法,就是在發(fā)送 FIN,CLOSE_WAIT 意思就是 wait close ,等待代碼中調(diào)用 close 方法,發(fā)送 FIN。(如果在服務(wù)器發(fā)現(xiàn)大量 CLOSE_WAIT 說明是代碼 bug !!哪里忘記了 close socket
- TIME_WAIT
主動發(fā)起 FIN 的一方,會進入 TIME_WAIT 。主動的這一方,收到對方的 FIN,并且返回 ACK 之后,就會進入這個狀態(tài),而不是直接進入 CLOSED 狀態(tài),在 TIME_WAIT 狀態(tài)下停留一定的時間,才會進入 CLOSED 徹底釋放連接。(TIME_WAIT 的意義,就是防止最后一個 ACK 丟失,萬一最后一個 ACK 丟失了,在 TIME_WAIT 狀態(tài)下,還可以重傳,而如果連接釋放了,就沒得重傳了?。。?br> ?
如:A 和 B 之間傳輸,到最后的一次相互傳輸中,在 A 這里如果是 CLOSED ,最后 A 給 B 返回數(shù)據(jù)的時候萬一丟失了包,A 連接釋放了,此時 B 會觸發(fā)超時重傳,B 重傳的 FIN 就會沒人來處理。如果在 A 這里是 TIME_WAIT ,等待一段時間沒見到 B 重傳的話,A 就可以放心的釋放連接了(如果 B 需要重傳早就重傳了)
?
TIME_WAIT 等待的時間是 2 MSL(操作系統(tǒng)中的一個配置參數(shù))MSL 表示兩個主機之間,數(shù)據(jù)從一邊到另一邊所花費的最大時間(一般來說 60s / 30s / 120s…)(為啥是 2 MSL ?等待的時間要包含 ACK 從 A -> B 重傳的 FIN 從 B -> A)
??四. 滑動窗口
- 提高傳輸效率的機制
剛才我們討論了確認應答策略,對每一個發(fā)送的數(shù)據(jù)段,都要給一個ACK確認應答。收到ACK后再發(fā)送下一個數(shù)據(jù)段。這樣做有一個比較大的缺點,就是性能較差。尤其是數(shù)據(jù)往返的時間較長的時候。
可靠性和效率是沖突的,保證可靠性肯定會影響到效率的!
TCP 在可靠性的前提下,盡可能的提高效率
提高效率的機制,本質(zhì)就是把等待 ACK 的時間重疊起來,減少了等待時間,就相當于提高了效率
原圖:每次傳輸都需要等待 ACK,收到 ACK 再發(fā)下一條數(shù)據(jù)
改動后:不再是一次發(fā)送一條,等待一條了,而是一次發(fā)送一批,等待一批 ACK 。
窗口大?。涸诓坏却那疤嵯?,最多一次發(fā)送 N 條數(shù)據(jù)。(N 就是窗口大?。?/p>
這里的 N 越大,則同時批發(fā)量的數(shù)據(jù)就越多,傳輸效率就越高!但是 N 也不是越大越好。傳輸效率 = 發(fā)送效率 & 接收效率。
這一批的數(shù)據(jù)會不會亂序?
?
網(wǎng)絡(luò)上任意傳輸?shù)臄?shù)據(jù),都可能會出現(xiàn)后發(fā)先至的情況!TCP 就會在接收緩沖區(qū)里"整隊"(TCP 數(shù)據(jù)上是帶有序號的)接收方就可以根據(jù)序號,來對數(shù)據(jù)進行重新排序。
其中灰色的一塊一塊的區(qū)域都是一個 TCP 數(shù)據(jù)報。其中白色的區(qū)域就是批量發(fā)送的。如圖一中 發(fā)送了 1001-2000 2001-3000 3001-4000 4001-5000 在針對這四個數(shù)據(jù)報,等待 ACK ,當 2001 ACK 回到 A 的時候,此時 1001-2000 這個數(shù)據(jù)就已經(jīng)被對方收到了,就可以繼續(xù)發(fā)送 5001-6000 這個數(shù)據(jù)了。
每次收到一個 ACK ,這里的窗口,都會對應的往后移動(繼續(xù)發(fā)后續(xù)的數(shù)據(jù)了)
如果出現(xiàn)了丟包,如何進行重傳?這里分兩種情況討論。
-
情況一:數(shù)據(jù)包已經(jīng)抵達,ACK被丟了。(丟包是個小概率事件,肯定是不會全丟的)
如果 1001 丟了,2001 到了,此時對于 A 來說,就知道 1-1000 這個數(shù)據(jù)也是到了的,最后一個會覆蓋前一個! -
情況二:數(shù)據(jù)包就直接丟了
數(shù)據(jù)包丟了,肯定要重傳?。?!啥時候觸發(fā)重傳,怎樣告知發(fā)送方要重傳?
如上主機 A 發(fā)了半天之后,發(fā)現(xiàn)好幾個連續(xù)的 1001,就明白了 1001 可能丟失了,接下來 A 就會重傳 1001 這個數(shù)據(jù)了!此處的原則是哪條丟了就重傳哪條,已經(jīng)傳輸過的數(shù)據(jù)就不用再重傳了,不必重復傳輸!快速重傳(不是重傳的有多快,而是沒有多余的冗余動作)
滑動窗口能提高效率,指的是相比于沒有滑動窗口,普通的確認應答。但是如果和無可靠性的傳輸相比(UDP),效率還是要差一些。與其說它是提高效率,不如說它是在補救低效率。
??五. 流量控制
- 本質(zhì)就是對滑動窗口的制約
滑動窗口,窗口大小越大,發(fā)送速率就越快!流量控制,就是針對發(fā)送速率進行制約!
整體的傳輸速率 = 發(fā)送速率 & 接收速率
如果發(fā)送速率 > 接收速率,這個時候繼續(xù)提高發(fā)送速率,就不能夠提高整體的效率了,反而會因為接收方丟包,觸發(fā)更多的重傳,反而還降低了速率。
要做的是,讓發(fā)送速率和接收速率相當(步調(diào)一致)
發(fā)送速率:發(fā)送數(shù)據(jù)的時候窗口大小,用于衡量發(fā)送速率
接收速率如何衡量?
圖中圈出來的部分操作的快慢就是衡量接收速率快慢的(和應用程序代碼相關(guān))
舉例:
流量控制,就是通過接收緩沖區(qū)剩余空間大小來作為下一次發(fā)送時候的窗口大小
接收方如何把接收緩沖區(qū)剩余空間大小告知發(fā)送方呢?
可以在 ACK 這個報文中帶上這個信息
當前是 ACK 報文的時候會生效,這個窗口大小,就表示了接收緩沖區(qū)的剩余空間大小,根據(jù)這個大小,就可以進一步的影響到發(fā)送速率了
16位表示的最大數(shù)值 64 KB 是否意味著窗口大小最大就是 64 KB 呢?
?
不是!我們可以有選項,也可以沒有,可以有一個,也可以有多個,這里有一個特殊的字段,窗口擴大因子~,窗口擴大因子可以是2,可以是4,可以是任何數(shù),相乘即可,如果沒有窗口擴大因子,默認是1。
需要注意的是在右邊接收緩沖區(qū)滿了的情況下,窗口大小就會被填成 0 ,此時發(fā)送方就會暫停發(fā)送數(shù)據(jù)!但是這里的暫停不會一直暫停,停了一會兒之后,會嘗試發(fā)送一個 “探測包”。
??六. 擁塞控制
流量控制,站在接收方的角度,來控制發(fā)送速率。但是整體的傳輸,其實不光有發(fā)送方和接收方,還有中間一系列用來轉(zhuǎn)發(fā)的設(shè)備!
控制 A 發(fā)的快慢,不僅考慮 B 的接收能力,也要考慮中間設(shè)備的轉(zhuǎn)發(fā)能力!
?
衡量 B 的處理能力,是使用了 B 的接收緩沖區(qū)的剩余空間
?
想要衡量中間的設(shè)備,咋辦?
- 中間的設(shè)備都有幾個?
- 中間的設(shè)備各個參數(shù)是啥?
- 兩次傳輸,經(jīng)歷的中間設(shè)備是否相同?
…
對于擁塞控制,采取的辦法是做實驗。通過實驗的方式,找到一個合適的窗口大?。?/font>
- 剛開始按照小的窗口來發(fā)送
- 如果不丟包,說明網(wǎng)絡(luò)中間環(huán)境比較暢通,就可以逐漸放大發(fā)送窗口的大小
- 放大到一定程度,速率已經(jīng)比較快,網(wǎng)絡(luò)上就容易出現(xiàn)擁堵,進一步出現(xiàn)丟包!當發(fā)送方發(fā)現(xiàn)丟包之后,就減小發(fā)送的窗口~
反復在 2-3 之間循環(huán)!這個過程就達到了一個 “動態(tài)平衡”
- 發(fā)送速率不慢,接近了能承載的極限
- 同時還可以盡量減少丟包
- 還能夠適應網(wǎng)絡(luò)環(huán)境的動態(tài)變化
流量控制 和 擁塞控制 都是通過控制窗口大小,來制約發(fā)送方的發(fā)送速率的,在保證可靠性的前提下,盡量提高一下發(fā)送的速度;都能影響發(fā)送方滑動窗口大?。∽罱K的滑動窗口大小,就取決于流量控制的窗口和擁塞控制的窗口的 " 較小值 " 。
- 如果是擁塞控制的窗口大,流量控制的窗口小,中間的節(jié)點轉(zhuǎn)發(fā)能力強,接收端的代碼,處理的慢。
- 如果是擁塞控制的窗口小,流量控制的窗口大,中間的節(jié)點轉(zhuǎn)發(fā)能力弱,接收端的代碼,處理的快。
?
其中擁塞控制的窗口大小是發(fā)送方自己做實驗做出來的,流量控制的窗口大小是接收方通過接收緩沖區(qū)剩余空間大小,通過 ACK 報文的報頭,返回給發(fā)送方的。最終發(fā)送方下一次發(fā)送窗口的大小,就是通過這兩個值的較小值來確定的。
上述測試是定性測試,如果是定量測試呢?(TCP 實現(xiàn)的時候,也是有明確的策略的,擁塞控制,窗口大小變化策略)
初始時候,擁塞窗口,從一個很小的數(shù)字開始,指數(shù)增長~(慢開始),剛開始的時候網(wǎng)絡(luò)環(huán)境是否擁堵我們不知道!先拿一個小的速率發(fā)送,是穩(wěn)健的做法!如果窗口大小到達閾值之后,就不再指數(shù)增長了,變成了線性增長。當線性增長達到一定程度之后,此時就可能丟包,這個時候直接把窗口大小回歸到一個特別小的窗口,重復上述的指數(shù)增長 / 線性增長的過程,同時,會把剛才線性增長的閾值進行調(diào)整。
上述策略是一種經(jīng)典策略,現(xiàn)實中還有一些更優(yōu)的改進方法
??七. 延遲應答
- 讓流量控制別限制的太狠
也是一個用來提高效率的機制,延時應答則是讓窗口能大一些!在流量控制中,通過 ACK 告知對方,窗口大?。ń邮站彌_區(qū)的空余空間)是多少合適
在這個等待的時間中,應用程序不停的在消費接收緩沖區(qū)(如果立即返回 ACK ,可能緩沖區(qū)剩余空間是 5 KB,稍等一會兒(例如500ms),在這個時間里,應用程序就可能取走了很多數(shù)據(jù),緩沖區(qū)的空余空間可能 100 KB了)
這種發(fā)送方式是滑動窗口來發(fā)送的,發(fā)送方是在批量發(fā)送數(shù)據(jù),所以不會對發(fā)送方等待時間造成很大影響,整體影響不大
在接收緩沖區(qū)少了一個 1001 應答報文,在延時應答的機制下,ACK 不一定要和發(fā)送的數(shù)據(jù)報一一對應,少點也可以,畢竟 2001 涵蓋了 1001
??八. 捎帶應答
在延遲應答的基礎(chǔ)上,我們發(fā)現(xiàn),很多情況下,客戶端服務(wù)器在應用層也是 “一發(fā)一收” 的。意味著客戶端給服務(wù)器說了 “How are you”,服務(wù)器也會給客戶端回一個 “Fine, thank you”;那么這個時候ACK就可以搭順風車,和服務(wù)器回應的 “Fine,thank you” 一起回給客戶端
正常情況下,ACK 是收到請求之后,內(nèi)核立即返回的;響應數(shù)據(jù),則是應用程序代碼發(fā)送的。所以他們是處于不同的時機發(fā)生的,不同的時機,就不能把上個 ACK 和下個響應報文合并。但是上面的延時應答,延時一會兒,就可能和返回響應的數(shù)據(jù),時間上就重合了。
響應在收到請求之后,多長時間之內(nèi)返回?不確定!可能快(幾個 ms),可能慢(幾百 ms)??斓臅r候本來數(shù)據(jù)報文也要發(fā)送,就和 ACK 搭著順風車走了;慢的時候 ACK 就先走了。
所以在上面的延時應答的條件下,在四次揮手中,中間的 ACK 是可能和下面的 FIN 一起發(fā)的,四次揮手就可能變成三次!
??九. 面向字節(jié)流
面向字節(jié)流,指的是讀寫載荷數(shù)據(jù)的時候,是按照 “字節(jié)流” 的方式來讀取的。TCP 數(shù)據(jù)報,本身仍然是一個一個 “數(shù)據(jù)報” 這樣的方式來傳輸?shù)?。(應用程序這里是感知不到從哪里到哪里是一個數(shù)據(jù)報的)
此時,應用程序,在讀取數(shù)據(jù)的時候,就可以很靈活的進行了,可以一次讀取 M 個字節(jié),分 N 次讀。
面向字節(jié)流的最核心問題:粘包問題!
- 如果一個 TCP 連接,里面只傳了一個應用層數(shù)據(jù)報,這個時候不會粘包(短連接)
- 如果一個 TCP 連接,里面?zhèn)鬏敹鄠€應用層數(shù)據(jù)報,這個時候就容易區(qū)分不清,從哪到哪是一個完整的應用層數(shù)據(jù)!
上述圖文中,這些數(shù)據(jù)都進入了接收緩沖區(qū),接收方也就區(qū)分不了,這些數(shù)據(jù)是來自于幾個應用層數(shù)據(jù)報,也區(qū)分不了從哪到哪是一個應用層數(shù)據(jù)報!
只要是面向字節(jié)流的傳輸,都有粘包問題(文件讀寫)
粘包問題解決方案:(在應用程序代碼中,明確包之間的邊界)
- 使用分隔符
- 約定長度
自定義應用層協(xié)議
通過上述方式,就可以明確從哪里到哪里,是一個完整的應用層數(shù)據(jù)報!
粘包問題,根本原因,是因為 TCP 面向字節(jié)流,但是直接影響應用層代碼!
??十. 異常處理
TCP 連接出現(xiàn)異常的時候,如何處理?
- 主機關(guān)機(按照固定的程序關(guān)機)
按照程序關(guān)機,會先殺死所有的用戶進程(也就包括咱們自己寫的 tcp 程序)
殺死進程 => 釋放進程 PCB => 釋放文件描述符表上對應的文件資源(相當于調(diào)用 close)
這個時候就會觸發(fā) FIN ,開啟四次揮手的流程!這里的異常比較好處理~
如果揮手揮完了,繼續(xù)關(guān)機沒事;如果揮手沒揮完,就已經(jīng)關(guān)機了,對端重傳 FIN 若干次,沒有響應,也就放棄了。
- 程序崩潰
同上,程序是正常關(guān)閉,還是異常崩潰,都會釋放 PCB,都會釋放文件描述符表(相當于調(diào)用 close)
也還是會正常四次揮手(雖然進程沒了,但是本身 TCP 連接也是內(nèi)核負責,內(nèi)核仍然會繼續(xù)完成后續(xù)的揮手過程)
- 主機掉電(突然拔電源)
筆記本還好,有內(nèi)置電源;臺式電腦就直接沒了,來不及揮手。
-
接收方掉電,對方嘗試發(fā)送數(shù)據(jù),發(fā)現(xiàn)沒有 ACK,嘗試重傳,重傳幾次,仍然沒有 ACK,發(fā)送方嘗試重新建立連接,如果重新建立也不成,認為是當前網(wǎng)絡(luò)出現(xiàn)了嚴重問題,也就自然放棄了。
-
發(fā)送方掉電,接收方就在等待發(fā)送方發(fā)送數(shù)據(jù),由于發(fā)送方掉電了,這個數(shù)據(jù)就發(fā)不過來,接收方不知道是對方?jīng)]發(fā)還是對方出了問題(接收方區(qū)分不了)。如果接收方一段時間沒有接收到數(shù)據(jù),就會定期的給發(fā)送方,發(fā)送 “心跳包” ,接收方給發(fā)送方發(fā)一個特殊的報文(ping),對方返回一個特殊的報文(pong),如果這個東西有了,就認為對方是正常的狀態(tài),如果 ping 沒有回應的 pong ,就認為對方掛了。
“心跳包” => 1. 周期性的 2. 判定對方是否存活的
“心跳包” 是非常重要的機制,TCP 里面有,應用程序有的時候也會實現(xiàn)心跳~
- 網(wǎng)線斷開
和主機掉電相同
??總結(jié)
- 前三個是保證可靠性機制
- 下一個是提高效率的機制
- 下兩個是保證可靠性機制
- 下兩個是提高效率的機制
- 下兩個是其他方面的問題
典型問題:如何使用 UDP 實現(xiàn)可靠傳輸?
?
在應用層代碼里面,參考 TCP 策略來實現(xiàn)~(TCP 咋做 咱就咋做)
拓展:TCP 和 UDP 對比?文章來源:http://www.zghlxwxcb.cn/news/detail-442139.html
什么時候使用 UDP 什么時候使用 TCP?文章來源地址http://www.zghlxwxcb.cn/news/detail-442139.html
- 如果需要關(guān)注可靠性傳輸,優(yōu)先考慮 TCP
- 如果傳輸?shù)膯蝹€數(shù)據(jù)報比較大(UDP 報文上限是 64kb)優(yōu)先考慮 TCP
- 使用 UDP,對于可靠性傳輸要求不高,但是對于性能要求很高(同一個機房內(nèi)部的主機之間通信,網(wǎng)絡(luò)環(huán)境簡單,寬帶充裕,并且又希望主機間通信能夠足夠快)
- 如果是需要進行 “廣播” ,優(yōu)先考慮 UDP(一個發(fā)送方,N 個接收方)(TCP 廣播就需要在應用層打開多個連接的方式來實現(xiàn)…)
到了這里,關(guān)于【網(wǎng)絡(luò)原理】TCP原理的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!