淺析NIO系列之TCP
一、前言
在spring boot 2中,默認(rèn)的web 容器是netty ,這說明“反應(yīng)式” 容器已經(jīng)是大勢所趨,無論是go 語言的協(xié)從線程,還是java 基于reactor 線程模型,都是基于事件編程實現(xiàn)高并發(fā)的實例。
在介紹NIO之前有必要了解下TCP協(xié)議,因為目前多數(shù)應(yīng)用都是給予應(yīng)用層進(jìn)行操作,導(dǎo)致隱藏了大量的網(wǎng)路細(xì)節(jié),知道這些細(xì)節(jié)以及原理對我們的問題排查很有益處。
二、TCP 特性
TCP 是一種面向連接的協(xié)議,它給用戶進(jìn)程提供可靠的全雙工的字節(jié)流。確保數(shù)據(jù)包的可靠,有序,以及支持流量控制。關(guān)于TCP 為何要做這些,我們從以下幾個方面入手:
1.IP網(wǎng)絡(luò)層為何不保證數(shù)據(jù)包的可靠性
2.TCP協(xié)議如何保證包可達(dá)、有序
3.TCP協(xié)議如何支持流量控制
4.TCP幾種狀態(tài)以及應(yīng)用
三、IP網(wǎng)絡(luò)層為何不保證數(shù)據(jù)包的可靠性
我們先看下OSI的網(wǎng)絡(luò)分層,在以下分層中,TCP 位于傳輸層,它保證的是協(xié)議的可靠性和連續(xù)性。具體的收發(fā)報是有底層的鏈路層以及物理層所決定的,所以TCP 所做的工作,也是基于底層的優(yōu)化和改進(jìn)。
客戶端與服務(wù)器之間的通信使用應(yīng)用協(xié)議,傳輸層的通信采用TCP協(xié)議,而TCP 協(xié)議又采用了更低的一層IP協(xié)議,IP則使用某種形式的數(shù)據(jù)鏈路層通信。
我們知道網(wǎng)絡(luò)的中的數(shù)據(jù),最終通過多個路由器連接傳送的。最底層的以太網(wǎng)協(xié)議規(guī)定了電子信號如何組成數(shù)據(jù)包,解決了局域網(wǎng)的點(diǎn)對點(diǎn)通信問題,但無法解決多個局域網(wǎng)的的互通問題。
而網(wǎng)絡(luò)層采用的IP協(xié)議,就是定義了一套自己的地址規(guī)則,主要解決尋址和路由的功能,根據(jù)對方的IP地址,尋找最佳路徑傳輸信息。局域網(wǎng)通過路由器連接,路由器基于IP協(xié)議,指導(dǎo)數(shù)據(jù)包向某個路由借口轉(zhuǎn)發(fā)。但I(xiàn)P協(xié)議不保證包一定到達(dá)以及完整性,特別是網(wǎng)絡(luò)擁堵的時候,會丟棄一些數(shù)據(jù)包,保證數(shù)據(jù)的傳送效率。
而保證數(shù)據(jù)包的完整、有序以及可靠,這就是TCP 協(xié)議要來做的事情了。
四、TCP 協(xié)議
4.1、TCP 包組成
很多網(wǎng)絡(luò)有一個最大傳送單元,它是鏈路層中的網(wǎng)絡(luò)對數(shù)據(jù)幀的一個限制,以以太網(wǎng)為例,MTU為1500個字節(jié)。一個IP數(shù)據(jù)報在以太網(wǎng)中 傳輸,如果它的長度大于該MTU值,就要進(jìn)行分片傳輸,使得每片數(shù)據(jù)報的長度小于MTU。
另外一個數(shù)據(jù)包還包含頭信息,除了自己的Tcp包頭,還有IP 頭信息和以太網(wǎng)頭信息。IP 數(shù)據(jù)包在以太網(wǎng)數(shù)據(jù)包的負(fù)載里面,最少需要20字節(jié),所以 IP 數(shù)據(jù)包的負(fù)載最多為1480字節(jié)。
那么tcp的一個包大小是多少吶?
我們需要機(jī)遇MSS這個值來確定,MSS是TCP里的一個概念(首部的選項字段中)。MSS是TCP數(shù)據(jù)包每次能夠傳輸?shù)淖畲髷?shù)據(jù)分段,TCP報文段的長度大于MSS時,要進(jìn)行分段傳輸。 如果不設(shè)置,則MSS的默認(rèn)值就為536個字節(jié) 。也就是說一個tcp包的在500字節(jié)左右。
4.2、如何保證可靠性
上述也說了,底層的路由轉(zhuǎn)發(fā)包,并不保證包的可靠性以及有序性。
首先為了保證包的完整性,TCP 會基于MSS 為大于 MSS的包進(jìn)行分包處理,默認(rèn)MSS大小為563byte,其大小小于MUT,以防止在網(wǎng)絡(luò)層被分片處理。
其次增加SEQ和ACK,同時采用超時重發(fā)的機(jī)制來保證包的可靠性。
1)SEQ
為了保證有序性,TCP 為每個包編配一個Sequence number ,簡稱 SEQ 。以便接收的一方按照順序還原。萬一發(fā)生丟包,也可以知道丟失的是哪一個包。一般第一個包的編號是一個隨機(jī)數(shù),也可以從1開始。
2)ACK
那么有編號了,如何確保包一定到達(dá)?
基于ACK 進(jìn)行確認(rèn)。對于接收方來說,每次接受一個包必須返回ack信息,發(fā)送端從而確認(rèn)這個包已經(jīng)傳送到。另外,接收方要對每一條報文做校驗。如果校驗發(fā)現(xiàn)出錯,則不發(fā)送確認(rèn)報文,從而觸發(fā)發(fā)送方超時重傳。
ACK 包含以下信息:
- 期待要收到下一個數(shù)據(jù)包的編號 next SEQ
- 接收方的接收窗口的剩余容量
我們采用wiershark抓包一個oschina的包看下三次握手的數(shù)據(jù)。
我的本機(jī)ip:192.168.1.103
oschinaIp:116.211.174.177
三次握手過程:
1.me->osChina:syn=1 seq=x ack=0
2.osChina->me:syn=1 seq=y ack=x+1
3.me->osChina:seq=x+1 ack=y+1
1、me->osChina:syn=1 seq=0 ack=0
2、osChina->me:syn=1 seq=0 ack=0+1
3、me->osChina:seq=0+1 ack=0+1
對比一下三次握手的過程。
3)超時重傳
我們知道網(wǎng)絡(luò)極其不穩(wěn)定,數(shù)據(jù)包即便增加了SEQ和ACK,能夠保證其有序性,但依然保證丟包或者超時的問題。如果發(fā)送端發(fā)送數(shù)據(jù),或者接收端回復(fù)ACK的消息在網(wǎng)絡(luò)中丟失或者超時怎么處理?
RTO ,超時重傳時間。要知道包是否出現(xiàn)超時,需要有一個評估方式,而RTT是對一個給定連接的往返時間的測量。由于網(wǎng)絡(luò)流量的變化,這個時間會相應(yīng)地發(fā)生改變,TCP需要跟蹤這些變化并動態(tài)調(diào)整超時時間RTO。
發(fā)送方如果一定時間內(nèi)沒收到報文的ACK,就認(rèn)為該報文丟失在網(wǎng)絡(luò)中了,自動重發(fā)該報文。這種機(jī)制稱之為超時重傳。
在這期間,如果接收端的消息,由于丟失,接收端沒有收到ack 消息,發(fā)送端會向接收端重發(fā)這個包。如果因為超時原因,發(fā)送端在超時定時器之后收到了這個包的ack 信息,而且發(fā)送端已經(jīng)重復(fù)發(fā)送了這個消息,此時發(fā)送端不會處理,直接丟棄該ack 。而接收端接收到了之后會再次回復(fù)ack 信息。
五、流量控制
上述中我們知道了TCP協(xié)議可以保證數(shù)據(jù)的可靠性,但是也得兼顧效率。兼顧效率的話需要考慮以下三個方面:
1.支持批量發(fā)包
2.能夠基于網(wǎng)絡(luò)的狀況,支持擁堵控制
3.能夠了解接收端的狀況,防止接收端處理不過來
基于以上三個需求,做了以下處理。
5.1、滑動窗口
如果TCP 中的包,都需要發(fā)送一個確認(rèn)一個的話,效率太低了,單次發(fā)送和確認(rèn)一個包,雖然保證了可靠性,但無法保證其效率。此時需要一個批量發(fā)送和確認(rèn)的方式,這就是滑動窗口所做的事情。
發(fā)送滑動窗口:
發(fā)送窗口從左向右移動在這個發(fā)送窗口之前的數(shù)據(jù)必然是已經(jīng)發(fā)送而且得到接收方確認(rèn)的數(shù)據(jù)落在發(fā)送窗口之內(nèi)的數(shù)據(jù)是發(fā)送方可以發(fā)送的數(shù)據(jù)在發(fā)送窗口之后的數(shù)據(jù)是不能發(fā)送的數(shù)據(jù)。
如果發(fā)生超時或者丟失現(xiàn)象。那么有兩種解決方案:
1、回退N,丟失的包號之后所有包都重發(fā)
2、選擇重傳ARQ,只發(fā)丟失的,避免重復(fù)的(效率高,防止發(fā)送重復(fù)的)
滑動窗口還有一個作用是讓發(fā)送端知道接收端的處理狀況。假設(shè)TCP接收方的緩存已經(jīng)滿了,無法處理更多的,而發(fā)送方并不知道,每次會給對方告知當(dāng)前滑動窗口的大小值 ,此時發(fā)送端就不會再發(fā)送數(shù)據(jù)了。
1.接收方接收到數(shù)據(jù)同樣馬上發(fā)送確認(rèn),但是同時對發(fā)送方宣布窗口大小為0。這樣發(fā)送方就暫時不會發(fā)送數(shù)據(jù)。
2.報文到達(dá)時不馬上發(fā)送確認(rèn),直到緩存有足夠的空間。這樣就可以避免發(fā)送方滑動窗口。但是這也存在一個問題,接收方延遲發(fā)送確認(rèn)的時間不應(yīng)該超過超時時間,如果過長會導(dǎo)致發(fā)送方誤以為數(shù)據(jù)丟失重新發(fā)送數(shù)據(jù)。
5.2、擁堵控制
我們知道網(wǎng)絡(luò)狀況有好友壞,好的時候,可以多發(fā)些包,壞的時候,如果發(fā)包速率不變的話,除了會加重網(wǎng)路負(fù)擔(dān)以外,還會造成包的過多丟失,除非更多的超時重發(fā),這無疑識降低了通信效率。
基于此,TCP通信雙方維護(hù)一個叫做擁塞窗口(cwnd,congesion window)的值,這個值取決于網(wǎng)絡(luò)中的擁塞率,發(fā)送方的發(fā)送窗口的值就等于擁塞窗口的大小。只要網(wǎng)絡(luò)中沒有出現(xiàn)擁塞,擁塞窗口的值就可以增大一些,這樣發(fā)送方可以發(fā)送到網(wǎng)絡(luò)中的數(shù)據(jù)就多一些。反之,擁塞窗口的值就減小,從而避免加劇網(wǎng)絡(luò)的擁塞率。
TCP目前擁塞控制主要有以下4種算法:
1.慢啟動
2.擁塞避免
3.快速重傳
4.快恢復(fù)
具體的算法實現(xiàn)方式就不再介紹了,大概實現(xiàn)的功能就是,基于當(dāng)前的網(wǎng)絡(luò)狀況,找到一個合適的發(fā)送速率,防止給網(wǎng)絡(luò)造成過大的負(fù)擔(dān)。比如說慢啟動,就是開始的時候,發(fā)送得較慢,然后根據(jù)丟包的情況,調(diào)整速率:如果不丟包,就加快發(fā)送速度;如果丟包,就降低發(fā)送速度。
六、TCP 狀態(tài)
了解TCP的都知道,TCP 建立連接的時候,有三次握手,斷開鏈接的時候又四次握手交互。那么其中的狀態(tài)是有哪些?
上面的圖看著是不是太亂記不住,我們看看下面這張梳理一下,看看具體應(yīng)用狀態(tài)。
從上面可以看到,連接建立成功的時候,其狀態(tài)是ESTABLISHED 的。當(dāng)接受端的狀態(tài)為SYN—RECV的時候,表示接受端,已經(jīng)回復(fù)第二次握手信息了,等待發(fā)送端再次確認(rèn)。如果網(wǎng)絡(luò)中遭受到大量的SYN 攻擊,會存在大量的SYN_RECV 狀態(tài)。此時可以定位這些問題IP ,通過防火墻過濾就能解決大量的假連接問題。
七、消失的連接——TIME_WAIT
在網(wǎng)絡(luò)中,某一端主動關(guān)閉而沒有通過四次握手關(guān)閉,此時tcp已經(jīng)建立的通道是否還在,多久會關(guān)閉?此時的TCP 狀態(tài)為TIME_WAIT ,可以想象,現(xiàn)實中經(jīng)常出現(xiàn)這種狀況,多數(shù)的關(guān)閉連接都是主動關(guān)閉而非通過協(xié)商通信關(guān)閉。那么此時關(guān)閉,若果再重連還能重連上之前的tcp 通道么,還是需要重現(xiàn)創(chuàng)建。
任何TCP實現(xiàn)必須為MSL選擇一個值,默認(rèn)是2分鐘或者30秒,TIME_WAIT默認(rèn)是2倍的MSL,持續(xù)時間在1-4分鐘之間。MSL是IP數(shù)據(jù)包能在網(wǎng)絡(luò)中存活的最長時間。
TIME_WAIT 存在的兩個理由:
1、可靠的實現(xiàn)TCP全雙工連接的終止
2、允許老的重復(fù)分節(jié)在網(wǎng)絡(luò)中消失
TCP必須防止某個連接的老的重復(fù)分組在該連接已經(jīng)終止后再現(xiàn),從而被誤解成屬于同一連接的化身,有time_wait 足夠長,是2倍的MSL的,那么足夠讓某個方向上的分組最多存活MSL秒就被丟棄。
從TIME_WAIT狀態(tài)到CLOSED狀態(tài),有一個超時設(shè)置,這個超時設(shè)置是 2*MSL(RFC793定義了MSL為2分鐘,Linux設(shè)置成了30s),如此超過了這個時間,當(dāng)前的tcp通道就會被定義為關(guān)閉。
以上就是淺析NIO系列之TCP的詳細(xì)內(nèi)容,更多關(guān)于NIO TCP的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
maven利用tomcat插件部署遠(yuǎn)程Linux服務(wù)器的步驟詳解
Maven已經(jīng)是Java的項目管理常用方式,下面這篇文章主要給大家介紹了關(guān)于maven利用tomcat插件部署遠(yuǎn)程Linux服務(wù)器的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11springboot利用AOP完成日志統(tǒng)計的詳細(xì)步驟
項目用到了過濾器,可能有的人會不理解,之所以用過濾器是因為想要在日志記錄post請求的json數(shù)據(jù)。本文重點(diǎn)給大家介紹springboot利用AOP完成日志統(tǒng)計的詳細(xì)步驟,感興趣的朋友跟隨小編一起看看吧2021-12-12高并發(fā)下如何避免重復(fù)數(shù)據(jù)產(chǎn)生技巧
這篇文章主要為大家介紹了高并發(fā)下如何避免重復(fù)數(shù)據(jù)的產(chǎn)生技巧詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07

一小時迅速入門Mybatis之bind與多數(shù)據(jù)源支持 Java API