目錄
1. TCP 協議格式
2. TCP 原理
(1) 確認應答
(2) 超時重傳
(3) 連接管理
a) 三次握手
b) 四次揮手
(4) 滑動窗口
(5) 流量控制
(6) 擁塞控制
(7) 延時應答
(8) 捎帶應答
3. TCP 特性
4. 異常情況的處理
1) 進程崩潰
2) 主機關機 (正常流程)
3) 主機掉電 (非正常)
4) 網線斷開
5. TCP 和 UDP 之間的對比
前文:TCP 的使用
1. TCP 協議格式
想要知道?TCP 的原理,我們首先就得了解 TCP 協議報文的格式。
TCP 數據報分為兩部分,報頭 + 載荷(應用層數據報),選項(可以有,也可以沒有)也是報頭中的一部分。
大家都知道,TCP 的特點是:有連接,可靠傳輸、面向字節流,全雙工。
其中可靠傳輸,是 TCP 最最最核心的特性(初心)。
可靠傳輸,不是說發送方能夠 100% 的傳輸給接收方(再厲害的技術,抵不過挖掘機一鏟子)
而是退而求其次:
1) 發送方將數據發出去之后,數據到沒到接收端,發送方能心里有數,知道接收方是否收到數據
2) 如果發現接收端沒收到數據,能采取一系列的措施進行補救。
那么 TCP 是如何保證可靠傳輸的呢?這就涉及到一個非常關鍵的機制,確認應答。
2. TCP 原理
(1) 確認應答
發送方,把數據發給接收端之后,接收端會返回一個應答報文(acknowledge, ack)。
發送方,如果收到這個應答報文了,就說明發送方把數據成功傳給對方了。
而在網絡傳輸過程中,因為每個數據包傳輸是走的路徑不同,所以就可能會出現數據包的 "先發后至" 的情況。
舉個例子:
為了處理這種情況,TCP 就需要完成兩個工作:
1. 確保應答報文和發出去的數據,能夠對得上號,不出現歧義。
2. 確保在出現先發后至的現象時,能夠讓應用程序這邊仍然按照正確的順序來理解數據。
而引入序號和確認序號,給每條數據進行編號,針對性應答,或者按照序號,對數據進行重新排序,就能解決上述問題。
TCP 是按照字節的方式來編序號的,如圖:
TCP 的初心就是實現可靠傳輸,達成可靠傳輸的核心機制就是確認應答,通過確認應答,發送方就能夠知道數據是否到達了接收方。如果數據到達了,那么接收方返回的 ack 里面的確認序號就是下一次發送方發送的數據的第一個字節。如果數據沒到達,那么接收方就不會返回 ack。(不考慮滑動窗口的情況下)
那么如何區分一個數據報是普通的數據,還是 ack 應答數據呢?
可以通過 TCP 報文協議中的,六位標志位的 ACK 來確認,數據報是否是應答報文。
如果 ACK = 1,則說明是應答報文,其中的 "確認序號字段" 就能夠生效。
如果 ACK = 0,那么數據報中的 "確認序號字段" 不會生效。
確認應答,是 TCP 最核心的機制,支持了 TCP 的可靠傳輸。
但是僅僅只有確認應答還不夠,還需要其他的機制來輔助,超時重傳就是這樣的一個輔助機制。
(2) 超時重傳
確認應答,描述的是一個比較理想的情況,
如果網絡傳輸中,出現丟包了,那么發送方就沒有辦法收到 ack 了,那該怎么辦呢?
通過超時重傳,就可以解決上述問題。超時重傳,是針對確認應答機制的補充。
超時重傳,就是等待一定的超時時間,發送方還沒有收到 ACK,發送方會主動把剛剛的數據重新傳輸一遍給接收端。
可以先來思考一下,為什么會出現丟包。
因為丟包是一個隨機的事件,所以在 TCP 傳輸過程中,丟包就存在兩種情況:
第一種是發送方發的數據報丟了,第二種是接收方發送的應答報文丟了。
如圖:
所以當引入可靠性的時候,是會付出代價的,最明顯的兩方面:
1. 傳輸效率? ? 因為有超時重傳,所以傳輸數據效率不高。(這也是 UDP 不會被 TCP 完全取代的意義)
2. 復雜程度
這里其實還有一個問題:
(3) 連接管理
連接管理就是建立連接和斷開連接。
其實 TCP 建立連接的過程也叫做三次握手,斷開連接的過程叫做四次揮手。
那這個握手到底是什么意思呢?
其實握手就是打個招呼,就是給對方傳輸一個簡短的,沒有業務數據的數據報,通過這個數據報來喚起對方的注意,從而觸發后續的操作。四次揮手的 "揮手" 和三次握手的 "握手" 是同一個意思。
舉個例子:比如說你走路遇到熟人的時候,對方主動跟你說 "你好" "hello" 之類的,打招呼的內容通常沒有什么實際的意義,就只是起到喚起對方的注意力的效果。
前面也提到過,TCP 是有連接的,需要主機雙方各自保存對端的信息。
a) 三次握手
那 TCP 的三次握手具體流程是怎樣的呢?
TCP 的三次握手,TCP 在建立連接的時候,需要通信雙方一共打三次招呼,才能完成建立連接。
TCP 的初心,是為了實現可靠傳輸,確認應答是核心,超時重傳等機制是輔助。
但進行確認應答和超時重傳有個大前提,那就是當前的網絡環境是基本可用的,通暢的,
如果當前網絡已經存在重大故障了,那么可靠傳輸是無從談起的。
三次握手的核心作用一:
投石問路,確認當前網絡是否是暢通的。
如果連 syn 和 ack 這樣沒攜帶業務數據的數據報都不能夠正常傳輸的話,那么之后要傳輸的攜帶了業務的數據報也不可能正常傳輸。
三次握手的核心作用二:
讓發送方和接收方都能確定自己的發送能力和接收能力均正常。
三次握手的核心作用三:
讓通信雙方,在握手過程中,針對一些重要的參數,進行協商。
TCP 通信過程中,序號是從幾開始,就是雙方協商出來的(一般不是從 1 開始),
每次連接建立的時候,都會協商出一個比較大的,和上次不太一樣的值。
這樣做是放了防止上一次遺留的數據,影響到本次數據的傳輸。
有的時候,網絡如果不太好,那么客戶端和服務器就會斷開,再重新建立連接,重連的時候,就有可能在新的連接建立好的時候,舊的數據姍姍來遲,這種遲到的數據報,是應該被丟棄的,而根據本次連接的正常數據報的序號,對比收到的數據報的序號,如果發現差別非常大的話,就說明收到的數據報是舊連接遲到的數據報,那就可以直接丟棄掉。
三次握手可以的話,那四次握手行不行?兩次握手行不行?
四次握手是可以的,但是沒必要,將中間的兩次合并成一次能夠提升效率。
兩次握手是不可以的,因為少了最后一次的握手,服務器就無法確定自己的發送能力是否正常和客戶端的接收能力是否正常。
b) 四次揮手
斷開連接的過程就是四次揮手,和三次握手類似。
TIME_WAIT 的意義就是當主動連接斷開方發送完最后一次 ACK 時,先進入 TIME_WAIT 狀態,等待 2MSL 的時間,如果在這個時間內,ACK 丟包了,對端重傳了 FIN,那么 主動斷開連接方就能馬上返回 ACK,這樣對端重傳的 FIN 才有意義。
MSL:是一個可配置的參數,這個參數數值是拍腦門拍出來的。
(4) 滑動窗口
前面的確認應答 ,超時重傳,連接管理都是用來保證 TCP 的可靠性的。
滑動窗口是用來提高效率的,其實是一種亡羊補牢。
TCP 因為引入了可靠傳輸,所以傳輸的效率不太高(多出了一些等待 ACK 的時間,單位時間內能傳輸的數據就減少了)
而滑動窗口,就是用來減小可靠傳輸對性能的影響的。
但是再怎么提高 TCP 的效率,也是不可能超過沒有引入可靠傳輸的 UDP 的效率的。
那么具體滑動窗口是怎么做的才能提高效率呢?
其實 TCP 慢就慢在要等對端的 ACK,那就可以在保證可靠傳輸的前提下,將等待時間縮小就好了。
那就可以進行批量傳輸數據,這樣做效率就上來了。
效率是提高了,但是 TCP 的核心是可靠傳輸,在提高效率的前提是數據能可靠傳輸。
上述滑動窗口中,確認應答是可以正常工作的。
但如果在滑動窗口的過程中,出現丟包了,那該怎么辦呢?
這里的重傳,相比于前面的超時重傳,有些不同。
還是得分兩種情況討論:
如果 ACK 全丟了呢?那此時的網絡肯定出現嚴重故障了,平時丟包率達到 10% 都算是比較嚴重的了,現在直接丟包率 100% ,就別想著 "可靠傳輸" 了。
如果通信雙方,傳輸數據的量比較小,也不頻繁,就仍然是普通的確認應答和超時重傳。
如果通信雙方,傳輸數據的量比較大,也更頻繁,就會進入到滑動窗口的模式,按照快速重傳的方式處理。
(5) 流量控制
通過滑動窗口的方式傳輸數據,效率是會提升的。
窗口越大,傳輸效率就會越大。(一份等待時間,等待的 ack 更多了,總的等待時間就更少了)
那么滑動窗口的窗口大小是設置的越大就越好嗎?
顯然不是的,提高效率的前提是保證可靠傳輸,如果因為傳輸的速度太快,接收方處理不過來,就會導致接收方出現丟包的情況,發送方還得重傳。
而流量控制,就是站在接收方的角度,反向制約發送方的傳輸速度。
發送方發送的速率,不應該超過接收方的處理能力。
那么如何知道接收方的處理能力是多少呢?
如圖,可以通過 接收方的接收緩沖區的剩余空間大小,來衡量處理能力的大小。
接收方每次收到數據之后,都會把接收緩沖區剩余空間大小通過 ack 返回給發送方,
發送方就會按照這個數值來調整下一輪的發送速度。
如圖:
(6) 擁塞控制
流量控制,考慮的是接收方的處理能力,
但是不僅僅要考慮到接收方的處理能力,還要考慮網絡通信過程中經過的節點(路由器/交換機)的處理能力,也就是說,還需要考慮整個通信的路徑,如果中間某個節點的傳輸速度達到了瓶頸,那么此時,也會對整體的傳輸產生影響。
擁塞控制,就是 考慮/衡量 通信過程中,中間節點的情況。
如圖:
但是關鍵的問題,在于怎么衡量中間節點。
之前接收方的處理能力,是很好衡量的。
由于中間節點,結構更復雜,更難以直接的進行量化。
因此可以使用 "實驗" 的方式,來找到合適的值。
可以讓發送方先按照比較低的速度開始發送數據(小窗口),如果數據傳輸過程非常順利,也沒有丟包,那就再嘗試使用更大的窗口,更高的速度進行發送(一點一點變化),隨著窗口不斷增大,達到一定程度,可能中間節點就會出現問題了,此時這個節點就可能會出現丟包。發送方發現丟包了,就把窗口大小再調整小,此時如果發現還是繼續丟包,那就繼續縮小。如果不丟包了,就繼續嘗試變大。
在這個過程中,發送方不停的調整自己的窗口大小,逐漸達成一個 "動態平衡".
這種做法,就相當于把 "中間節點" 視為一個整體,通過 "實驗" 的方式,來找到中間節點的瓶頸在哪里。
上述過程如圖:
流量控制和擁塞控制都是在限制發送方的發送窗口大小,
最終實際發送的窗口大小,是取?流量控制 和 擁塞控制 中的較小值。
(7) 延時應答
A 把數據傳給 B,B 就會馬上返回 ack 給 A (正常)。
也有的時候,A 傳輸給 B,此時 B 等一會再返回 ack 給 A (延時應答)。
本質上也是為了提升傳輸效率。
發送方的窗口大小,就是傳輸效率的關鍵。
流量控制這里,就是根據接收方的接收緩沖區的剩余空間,來控制發送方的發送速率的,
如果有辦法,能讓流量控制得到的窗口更大點,發送速率就更快點(大點的前提是,能讓接收方處理的過來)
通過延時返回 ack,給接收方更多的時間讀取接收緩沖區的數據,此時接收方讀了這個數據之后,緩沖區的剩余空間,就變大了,返回的窗口大小,也就更大了。
比如,初始情況下,緩沖區的剩余空間是 10kb,如果立即返回 ack,就返回了 10kb 這么大的大小窗口。如果延時個 200ms 再返回,那么在這 200ms 的過程中,接收方的應用程序,又讀了 2kb,此時,返回的 ack 就是返回 12kb 的窗口了。
延時應答,才促成了前面的四次揮手,能夠三次揮完。
(8) 捎帶應答
在延時應答的基礎上,進一步提高效率。
3. TCP 特性
TCP 的特性是:有連接,可靠傳輸,面向字節流,全雙工
面向字節流的特性是:
傳輸數據的時候可以非常靈活,可以一次傳輸一個字節,也可以一次傳輸多個字節。
但是這里存在一個問題:粘包問題(不是 tcp 獨有的,而是面向字節流的機制都有類似的情況)
這里的包指的是應用層數據包,如果同時有多個應用層數據包被傳輸過去,此時就容易出現粘包問題。
如圖:
那么該如何解決粘包問題呢?
核心思路:通過定義好應用層協議,明確應用層數據報之間的邊界。
1. 引入分隔符
2. 引入長度
比如使用 \n 作為分隔符:
引入長度:
4. 異常情況的處理
如果在使用 tcp 的過程中,出現意外,會如何處理?
1) 進程崩潰
進程崩潰,本質上就是進程沒了,進程終止了,那么文件描述符表就釋放了,也就相當于調用 socket.close() ,此時就會觸發 FIN,對方收到之后,自然也就會返回 ACK 和 FIN,這邊再進行 ACK,這里就是一個正常的四次揮手斷開連接的流程。
TCP 的連接,可以獨立于進程存在。(進程沒了,TCP 連接不一定沒)
2) 主機關機 (正常流程)
在進行關機的時候,就是會先觸發強制終止進程的操作(相當于 1)
此時就會觸發 FIN,對方收到之后,自然會返回 ACK 和 FIN。
此時,不僅僅是進程沒了,整個系統也關閉了。如果在系統關閉之前,對端返回的 ACK 和 FIN 到了,此時系統還是可以返回 ACK,進行正常的四次揮手的。如果系統已經關閉了,ACK 和 FIN 遲到了,就無法進行后續 ACK 的響應。站在對端的角度,以為是自己的 FIN 丟包了,就會重傳 FIN,連續重傳好幾次都沒有響應,最后對端就會放棄連接(把持有的對端信息刪除)。
3) 主機掉電 (非正常)
主機掉電,是一瞬間的事情,還來不及殺進程,也來不及發送 FIN,主機直接就停機了。
1. 如果對端是在發送數據(接收方掉電),發送的數據就會一直等待 ack,觸發超時重傳,重傳好幾次還是沒有響應,就會觸發 TCP 的連接重置功能,發起復位報文段(RST = 1),如果復位報文段發過去之后也沒有效果,此時就會釋放連接了。
2. 如果對端是在接收數據(發送方掉電),對端還在等待數據到達,等了半天沒消息,此時其實也無法區分,是發送方沒發消息,還是發送方掛了。
針對這種情況,TCP 提供了心跳包的機制,接收方也會周期性的給發送方發送一個特殊的,不攜帶業務數據的數據包,并且期望對方返回一個應答,如果對方沒有應答,并且重復了多次之后,仍然沒有,就視為對方掛了,此時就可以單方面釋放連接了。
4) 網線斷開
網線斷開和剛剛的主機掉電非常類似。
如何識別某個機器是否掛了,一般都是通過心跳來檢測的。
5. TCP 和 UDP 之間的對比
TCP 有連接,可靠傳輸,面向字節流,全雙工。
UDP 無連接,不可靠,面向數據報,全雙工。
TCP 的優勢是可靠傳輸,TCP 適用于絕大部分場景。
UDP 的優勢是更高效率,UDP 適合于對 "可靠性不敏感","性能敏感" 的場景,比如局域網內部(同一個機房)的主機之間的通信。
如果要傳輸比較大的數據包,TCP 優先。(UDP 有 64kb 的限制)
如果要進行 "廣播傳輸" ,優先考慮 UDP。UDP 天然支持廣播,TCP 不支持(得自己寫代碼實現)