面試官偶爾會問到TCP相關(guān)的知識點(diǎn),在最早之前我是一臉懵逼答非所問的,故整理了一下關(guān)于TCP的相關(guān)知識點(diǎn),希望對大家有所收獲!
為了更進(jìn)一步了解網(wǎng)絡(luò)層面的知識,先曬出一張網(wǎng)絡(luò)體系結(jié)構(gòu)圖,加深理解
計算機(jī)網(wǎng)絡(luò)體系結(jié)構(gòu)圖
TCP UDP的區(qū)別
TCP | UDP |
---|---|
面向連接的協(xié)議?;谶@種連接方式, 通信設(shè)備應(yīng)在傳輸數(shù)據(jù)前建立連接,并應(yīng)在傳輸數(shù)據(jù)后關(guān)閉連接 | 面向數(shù)據(jù)報的協(xié)議。意味著打開、維護(hù)、終止連接不會有開銷。UDP對于廣播和多播類型的網(wǎng)絡(luò)傳輸是有效的 |
點(diǎn)對點(diǎn)通信,連接兩端的socket | |
面向字節(jié)流。TCP把傳輸?shù)母鞣N數(shù)據(jù)當(dāng)做無結(jié)構(gòu)的字節(jié)流來用 | |
可靠性。它能夠保證向目標(biāo)路由器的數(shù)據(jù)傳輸 | 不可靠性。不能保證向目的地傳送數(shù)據(jù) |
錯誤檢測機(jī)制。TCP提供了廣泛的錯誤檢查機(jī)制,這是因為它提供流量控制和數(shù)據(jù)確認(rèn)。 | UDP只有使用校驗和的基本錯誤檢查機(jī)制 |
數(shù)據(jù)排序。數(shù)據(jù)包能夠按照順序到達(dá)接收器 | 沒有數(shù)據(jù)排序。若有需求,則需要再應(yīng)用程序?qū)舆M(jìn)行管理 |
速度較慢。相對UDP而言速度較慢。 | 快、簡單、高效 |
重傳機(jī)制。支持重傳丟失的數(shù)據(jù)包 | 無重傳機(jī)制 |
標(biāo)頭大小為20個字節(jié) | 標(biāo)頭大小為8個字節(jié) |
重量級 | 輕量級 |
用于HTTP,HTTP,F(xiàn)TP,SMTP和Telnet | 用于DNS,DHCP,TFTP,SNMP,RIP和VoIP |
注:本文所指的Client 均為發(fā)送方,Server為接收方
TCP 三次握手、四次揮手
三次握手
建立一個TCP連接時,需要Client和Server總共發(fā)送3個包。
三次握手的目的是連接服務(wù)器指定端口,建立 TCP 連接,并同步連接雙方的序列號和確認(rèn)號,交換 TCP 窗口大小信息。在 socket 編程中,客戶端執(zhí)行
connect()
時。將觸發(fā)三次握手。
-
Step 1(SYN).
Client 端要和Server端 建立連接,所以要發(fā)一個SYN(即同步序列號)的包,初始序號
x
,保存在包頭的序列號(Sequence Number)字段里,指明打算連接的Service port,。用于告知Server:我(Client)可能要與你開始通訊了,現(xiàn)在發(fā)給你一個我(Client)啟動段的序列號。
此時Client進(jìn)入
SYN_SEND
狀態(tài) -
Step 2(SYN+ACK).
Server 端 接收到數(shù)據(jù)包(通知)后使用一個SYN-ACK信號位設(shè)置,來響應(yīng)Client 端的請求。
即發(fā)送了自己的序列號(SVN),初始序號為
y
,和確認(rèn)號(ACK,即Client發(fā)來的序列號遞增1, 即x + 1
)。此時Server進(jìn)入
SYN_RCVD
狀態(tài) -
Step 3(ACK).
Client接收到Server端的響應(yīng)后
發(fā)送確認(rèn)包(ACK,即Server 發(fā)來的序列號遞增1, 即
y + 1
)來確認(rèn)收到響應(yīng),此時Client 進(jìn)入ESTABLISHED
狀態(tài),當(dāng)Server 接收到該ACK包后,也進(jìn)入ESTABLISHED
狀態(tài)。
四次揮手
TCP 的連接的拆除需要發(fā)送四個包,因此稱為四次揮手(Four-way handshake)。
需要四個包的原因是是因為TCP的半關(guān)閉引起的
客戶端或服務(wù)器均可主動發(fā)起揮手動作,在 socket 編程中,任何一方執(zhí)行
close()
操作即可產(chǎn)生揮手操作。
下面假設(shè)Client主動發(fā)起揮手動作
-
Step 1(FIN).
Client 端(發(fā)起方)要關(guān)閉TCP連接,所以要發(fā)一個FIN包,序號為
x
。發(fā)送完畢后,此時Client進(jìn)入
FIN_WAIT_1
狀態(tài)(此時表明無數(shù)據(jù)可發(fā)送,但仍可接受數(shù)據(jù)) -
Step 2(ACK).
當(dāng)Server 端 接收到FIN包后,立即向Client發(fā)送確認(rèn)包(即Client發(fā)來的FIN包的序號遞增1,
x + 1
)。發(fā)送完畢后,此時Server 進(jìn)入
CLOSE_WAIT
狀態(tài)(此時表明接收到了Client的關(guān)閉,但還沒做好“思想準(zhǔn)備“關(guān)閉連接)當(dāng)Client 端 接收到ACK包后,進(jìn)入
FIN_WAIT_2
狀態(tài) -
Step 3(FIN).
Server 端 發(fā)送ACK包一段時間(這段時間它有一些關(guān)閉過程)后,開始發(fā)送FIN包,序號為
y
發(fā)送完畢后,此時Server 進(jìn)入
LAST_ACK
狀態(tài) -
Step 4(ACK).
當(dāng)Client 端 接收到FIN包,即關(guān)閉請求后,發(fā)送一個確認(rèn)包(即Server發(fā)來的FIN包的序號遞增1,
y + 1
)發(fā)送完畢后,此時Client 進(jìn)入
TIME_WAIT
狀態(tài),目的在于在時間周期n內(nèi),允許Client 在發(fā)送的ACK包丟失的情況下重新發(fā)當(dāng)Server 端接收到ACK包后,連接正式關(guān)閉,此時Server進(jìn)入
CLOSED
狀態(tài)。Client資源(包括端口號、緩沖區(qū)數(shù)據(jù))都被釋放當(dāng)Client在時間周期n結(jié)束后,仍沒收到Server 發(fā)的ACK包,則認(rèn)為已正常關(guān)閉連接,此時Client 也進(jìn)入
CLOSE
狀態(tài)
TCP協(xié)議如何保證可靠傳輸
從上面的體系圖可以看到,TCP(即運(yùn)輸層)的報文信息最終會交付到網(wǎng)際層。而網(wǎng)際層不會提供可靠的服務(wù)。所以還是要TCP來保證可靠的傳輸,才能最終保證數(shù)據(jù)服務(wù)的可靠
原理
? 宏觀上看,從TCP的特性可以得知,它有自己的錯誤檢測機(jī)制、數(shù)據(jù)按序傳輸、確認(rèn)應(yīng)答+序列號、支持重傳的功能。然后具體的內(nèi)部處理是怎樣的呢?主要有以下兩點(diǎn)
-
停止等待協(xié)議
這是最簡單的保證可靠傳輸?shù)膮f(xié)議
以下會發(fā)生兩種情況
-
無差錯
停止等待協(xié)助-無差錯.jpg
可以看到Client 在發(fā)送分組M1(即數(shù)據(jù)單元)后,暫停,等到Server發(fā)回確認(rèn)后,繼續(xù)發(fā)送下一個分組...
這是理想條件下的無差錯情況
-
有差錯
停止等待協(xié)助-超時重傳.jpg
-
當(dāng)Client 發(fā)送 分組M1(會先設(shè)置一個計時器,在此計時器內(nèi)M1仍存在,以便重傳)時,可能會遇到數(shù)據(jù)無法到達(dá)Server,或者Server 檢測出問題并丟棄了它,在指定時間內(nèi)Client 如果未收到來自Server 的確認(rèn),則會重傳M1,即人們常說的超時重傳
超時重傳會有以下情況
-
確認(rèn)丟失(發(fā)回延遲)
Client發(fā)送分組M1,Server收到M1并發(fā)送確認(rèn)分組,而在指定的時間內(nèi)Client沒有收到確認(rèn),后會重傳M1.
而由于Server 已經(jīng)收到過M1了, 所以此時它需要
丟棄M1分組, 發(fā)送確認(rèn)分組
-
確認(rèn)遲到(發(fā)送延遲)
由于網(wǎng)絡(luò)延遲等原因,Client發(fā)送的分組M1,在指定時間后才到Server, 此時Client 還沒來得及收到確認(rèn),再次發(fā)送分組M1
而由于Server 剛好收到了M1,所以此時它需要
丟棄M1分組, 發(fā)送確認(rèn)分組
,Client
收到>=2個以上的確認(rèn),會執(zhí)行丟棄操作,并且停止發(fā)送
-
連續(xù)ARQ協(xié)議
由于停止等待協(xié)議對信道的利用率太低,故可以采用流水線的方式來傳輸,即連續(xù)ARQ協(xié)議。
這里需要提到一個
發(fā)送窗口
的概念。發(fā)送窗口支持滑動,所以也有滑動窗口
這么一個概念? Client 會維護(hù)一個發(fā)送窗口,一個窗口內(nèi)可以有多個連續(xù)分組進(jìn)行發(fā)送,而不必等待對方的確認(rèn)一條條分組發(fā)。
? Server 亦不會對每個分組進(jìn)行回傳確認(rèn),而是在按需發(fā)送到達(dá)的最后一個分組到達(dá)之后,發(fā)送確認(rèn),代表這個窗口的分組已經(jīng)發(fā)送成功
具體實(shí)現(xiàn)
1. 使用滑動窗口
? 窗口主要分為接收窗口和發(fā)送窗口
-
接收窗口
接收窗口.png
“接收窗口”大小取決于應(yīng)用(比如說tomcat:8080端口的監(jiān)聽進(jìn)程)、系統(tǒng)、硬件的限制。圖中,接收窗口是31~50,大小為20。
在接收窗口中,黑色的表示已收到的數(shù)據(jù),白色的表示未收到的數(shù)據(jù)。
當(dāng)收到窗口左邊的數(shù)據(jù),如27,則丟棄,因為這部分已經(jīng)交付給主機(jī);
當(dāng)收到窗口右邊的數(shù)據(jù),如52,則丟棄,因為還沒輪到它;
當(dāng)收到已收到的窗口中的數(shù)據(jù),如32,丟棄;
當(dāng)收到未收到的窗口中的數(shù)據(jù),如35,緩存在窗口中。
- 發(fā)送窗口
? 發(fā)送窗口的大小swnd=min(rwnd,cwnd)。rwnd是接收窗口,cwnd用于擁塞控制,暫時可以理解swnd= rwnd =20。
圖中分為四個區(qū)段,其中P1到P3是發(fā)送窗口。
tips:發(fā)送窗口以字節(jié)為單位。為了方便畫圖,圖中展示得像以報文為單位一樣。但這不影響理解。
2. 重傳與確認(rèn)
-
確認(rèn)
這里主要是通過累計確認(rèn)的方式
-
重傳
這里主要是上面說的
超時重傳
,每一個報文都會有超時計數(shù)器,當(dāng)超過指定時間后,Client(發(fā)送方)會觸發(fā)重傳報文
3. 流量控制(基于滑動窗口)
? 流量即發(fā)送方發(fā)送的報文流量。當(dāng)接收方來不及處理數(shù)據(jù)時,通過滑動窗口,告訴發(fā)送方能夠接受的單位字節(jié)是多少,以降低發(fā)送的頻率,防止包丟失
在建立連接時,接收方(B),告訴了發(fā)送方(A):
我的接收窗口是400(單位字節(jié))
.圖中的
ACK
為TCP首部的ACK字段,ack
為首部的確認(rèn)號字段.流量控制體現(xiàn)在:
rwnd=300, rwnd=100, rwnd=0
.在確認(rèn)報文的窗口字段設(shè)定了發(fā)送方能夠發(fā)出的數(shù)據(jù)多少,從而控制流量.注意只有到首部的ACK
字段值為1,窗口字段的值才有效.假設(shè)在B發(fā)送了
rwnd=0
之后,過段時間由于自己又希望接收到數(shù)據(jù),于是發(fā)出rwnd=400
的報文,但是該報文丟失了,這樣A依然無法發(fā)送數(shù)據(jù),B希望接收但接收不到數(shù)據(jù).
? 為解決該問題,TCP為每個鏈接都設(shè)有一個持續(xù)計時器
.只要接收到對方窗口為0的通知,就啟動持續(xù)計時器.在計時器到期后,就發(fā)送探測報文
,對方可以在該報文的確認(rèn)中告知當(dāng)前的窗口值.若窗口任然為0,那么就重新設(shè)定計時器,若不為0,那么上述的問題就解決了.
4. 擁塞控制
? 擁塞是指對網(wǎng)絡(luò)某一資源(帶寬,緩存等)的需求超過了可提供的部分,從而使網(wǎng)絡(luò)中傳送的數(shù)據(jù)不能按時到達(dá),網(wǎng)絡(luò)性能變差的情況.
擁塞控制就是防止過多的數(shù)據(jù)注入到網(wǎng)絡(luò)中,這樣網(wǎng)絡(luò)中的資源壓力就小了.
流量控制和擁塞控制似乎很相似,但是他們不同.前者立足于接收和發(fā)送者雙方的情況;而后者注重的是數(shù)據(jù)量對網(wǎng)絡(luò)環(huán)境的影響
TCP 粘包、拆包
由于TCP 是一個面向字節(jié)流的協(xié)議,這也決定了它的數(shù)據(jù)是無結(jié)構(gòu)的。所以TCP無法得知應(yīng)用層對于這快數(shù)據(jù)的定義,而是基于自身緩沖區(qū)的實(shí)際情況進(jìn)行數(shù)據(jù)包的拆分,或者將多個數(shù)據(jù)包進(jìn)行合并來發(fā)送。
參考下圖,在不同的條件下,會發(fā)生多種現(xiàn)象
- Server 分別接收P1,P2,沒有發(fā)生粘包、拆包
- Server 一次接收P1+P2兩個報文,發(fā)生了粘包
- Server 先接收P2, 再分別接收了P1_1, P1_2,發(fā)生了拆包
- Server 先接收了P2+P1_2,再接收了P1_1,發(fā)生了粘包、拆包
- 另一種極端情況,當(dāng)窗口非常小,恰逢P(guān)1又很大時,可能會發(fā)生多次對P1進(jìn)行拆包
首先我們要知道,發(fā)送的數(shù)據(jù)會先傳入發(fā)送緩沖區(qū),再通過網(wǎng)絡(luò)傳輸發(fā)送到接收端的緩沖區(qū)
以上現(xiàn)象發(fā)生的原因主要是
- 發(fā)送的字節(jié) 大于 TCP發(fā)送緩沖區(qū)的大小,會發(fā)生拆包
- 發(fā)送的報文 大于 MSS(最大報文長度),會發(fā)生拆包
- 發(fā)送的字節(jié) 小于 TCP發(fā)送緩沖區(qū)的大小,會將多次寫入緩沖區(qū)的報文一并發(fā)送,即發(fā)生粘包
解決方案,需要上層應(yīng)用程序做對應(yīng)的處理
-
規(guī)定報文長度
。例如設(shè)定每條報文固定長度為200字節(jié),當(dāng)不夠時,用空格填充 -
報文末尾添加回車換行符
。例如FTP協(xié)議 -
將報文分為header and body
,在頭部中聲明報文長度,然后根據(jù)這個長度來獲取報文
我們常用的Netty 已經(jīng)幫我們處理好這些問題,我們僅需調(diào)用特定的方法即可。這個在后續(xù)的Netty挖掘機(jī)系列文章會提到栗子。
比如有:
-
LineBasedFrameDecoder
基于換行符解決 -
DelimiterBasedFrameDecoder
基于分隔符解決 -
FixedLengthFrameDecoder
指定長度解決
參考鏈接:【讀】這一次,讓我們再深入一點(diǎn) - TCP協(xié)議
參考鏈接:什么是 TCP 拆、粘包?如何解決?