淺析NIO系列之TCP

一、前言

在spring boot 2中,默認的web 容器是netty ,這說明“反應式” 容器已經是大勢所趨,無論是go 語言的協從線程,還是java 基於reactor 線程模型,都是基於事件編程實現高並發的實例。

在介紹NIO之前有必要瞭解下TCP協議,因為目前多數應用都是給予應用層進行操作,導致隱藏瞭大量的網路細節,知道這些細節以及原理對我們的問題排查很有益處。

二、TCP 特性

TCP 是一種面向連接的協議,它給用戶進程提供可靠的全雙工的字節流。確保數據包的可靠,有序,以及支持流量控制。關於TCP 為何要做這些,我們從以下幾個方面入手:

1.IP網絡層為何不保證數據包的可靠性

2.TCP協議如何保證包可達、有序

3.TCP協議如何支持流量控制

4.TCP幾種狀態以及應用

三、IP網絡層為何不保證數據包的可靠性

我們先看下OSI的網絡分層,在以下分層中,TCP 位於傳輸層,它保證的是協議的可靠性和連續性。具體的收發報是有底層的鏈路層以及物理層所決定的,所以TCP 所做的工作,也是基於底層的優化和改進。

客戶端與服務器之間的通信使用應用協議,傳輸層的通信采用TCP協議,而TCP 協議又采用瞭更低的一層IP協議,IP則使用某種形式的數據鏈路層通信。

我們知道網絡的中的數據,最終通過多個路由器連接傳送的。最底層的以太網協議規定瞭電子信號如何組成數據包,解決瞭局域網的點對點通信問題,但無法解決多個局域網的的互通問題。

而網絡層采用的IP協議,就是定義瞭一套自己的地址規則,主要解決尋址和路由的功能,根據對方的IP地址,尋找最佳路徑傳輸信息。局域網通過路由器連接,路由器基於IP協議,指導數據包向某個路由借口轉發。但IP協議不保證包一定到達以及完整性,特別是網絡擁堵的時候,會丟棄一些數據包,保證數據的傳送效率。

而保證數據包的完整、有序以及可靠,這就是TCP 協議要來做的事情瞭。

四、TCP 協議

4.1、TCP 包組成

很多網絡有一個最大傳送單元,它是鏈路層中的網絡對數據幀的一個限制,以以太網為例,MTU為1500個字節。一個IP數據報在以太網中 傳輸,如果它的長度大於該MTU值,就要進行分片傳輸,使得每片數據報的長度小於MTU。

另外一個數據包還包含頭信息,除瞭自己的Tcp包頭,還有IP 頭信息和以太網頭信息。IP 數據包在以太網數據包的負載裡面,最少需要20字節,所以 IP 數據包的負載最多為1480字節。

那麼tcp的一個包大小是多少吶?

我們需要機遇MSS這個值來確定,MSS是TCP裡的一個概念(首部的選項字段中)。MSS是TCP數據包每次能夠傳輸的最大數據分段,TCP報文段的長度大於MSS時,要進行分段傳輸。 如果不設置,則MSS的默認值就為536個字節 。也就是說一個tcp包的在500字節左右。

4.2、如何保證可靠性

上述也說瞭,底層的路由轉發包,並不保證包的可靠性以及有序性。

首先為瞭保證包的完整性,TCP 會基於MSS 為大於 MSS的包進行分包處理,默認MSS大小為563byte,其大小小於MUT,以防止在網絡層被分片處理。

其次增加SEQ和ACK,同時采用超時重發的機制來保證包的可靠性。

1)SEQ

為瞭保證有序性,TCP 為每個包編配一個Sequence number ,簡稱 SEQ 。以便接收的一方按照順序還原。萬一發生丟包,也可以知道丟失的是哪一個包。一般第一個包的編號是一個隨機數,也可以從1開始。

2)ACK

那麼有編號瞭,如何確保包一定到達?

基於ACK 進行確認。對於接收方來說,每次接受一個包必須返回ack信息,發送端從而確認這個包已經傳送到。另外,接收方要對每一條報文做校驗。如果校驗發現出錯,則不發送確認報文,從而觸發發送方超時重傳。

ACK 包含以下信息:

  • 期待要收到下一個數據包的編號 next SEQ
  • 接收方的接收窗口的剩餘容量

我們采用wiershark抓包一個oschina的包看下三次握手的數據。

我的本機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)超時重傳

我們知道網絡極其不穩定,數據包即便增加瞭SEQ和ACK,能夠保證其有序性,但依然保證丟包或者超時的問題。如果發送端發送數據,或者接收端回復ACK的消息在網絡中丟失或者超時怎麼處理?

RTO ,超時重傳時間。要知道包是否出現超時,需要有一個評估方式,而RTT是對一個給定連接的往返時間的測量。由於網絡流量的變化,這個時間會相應地發生改變,TCP需要跟蹤這些變化並動態調整超時時間RTO。

發送方如果一定時間內沒收到報文的ACK,就認為該報文丟失在網絡中瞭,自動重發該報文。這種機制稱之為超時重傳。

在這期間,如果接收端的消息,由於丟失,接收端沒有收到ack 消息,發送端會向接收端重發這個包。如果因為超時原因,發送端在超時定時器之後收到瞭這個包的ack 信息,而且發送端已經重復發送瞭這個消息,此時發送端不會處理,直接丟棄該ack 。而接收端接收到瞭之後會再次回復ack 信息。

五、流量控制

上述中我們知道瞭TCP協議可以保證數據的可靠性,但是也得兼顧效率。兼顧效率的話需要考慮以下三個方面:

1.支持批量發包

2.能夠基於網絡的狀況,支持擁堵控制

3.能夠瞭解接收端的狀況,防止接收端處理不過來

基於以上三個需求,做瞭以下處理。

5.1、滑動窗口

如果TCP 中的包,都需要發送一個確認一個的話,效率太低瞭,單次發送和確認一個包,雖然保證瞭可靠性,但無法保證其效率。此時需要一個批量發送和確認的方式,這就是滑動窗口所做的事情。

發送滑動窗口:

發送窗口從左向右移動在這個發送窗口之前的數據必然是已經發送而且得到接收方確認的數據落在發送窗口之內的數據是發送方可以發送的數據在發送窗口之後的數據是不能發送的數據。

如果發生超時或者丟失現象。那麼有兩種解決方案:

1、回退N,丟失的包號之後所有包都重發

2、選擇重傳ARQ,隻發丟失的,避免重復的(效率高,防止發送重復的)

滑動窗口還有一個作用是讓發送端知道接收端的處理狀況。假設TCP接收方的緩存已經滿瞭,無法處理更多的,而發送方並不知道,每次會給對方告知當前滑動窗口的大小值 ,此時發送端就不會再發送數據瞭。

1.接收方接收到數據同樣馬上發送確認,但是同時對發送方宣佈窗口大小為0。這樣發送方就暫時不會發送數據。

2.報文到達時不馬上發送確認,直到緩存有足夠的空間。這樣就可以避免發送方滑動窗口。但是這也存在一個問題,接收方延遲發送確認的時間不應該超過超時時間,如果過長會導致發送方誤以為數據丟失重新發送數據。

5.2、擁堵控制

我們知道網絡狀況有好友壞,好的時候,可以多發些包,壞的時候,如果發包速率不變的話,除瞭會加重網路負擔以外,還會造成包的過多丟失,除非更多的超時重發,這無疑識降低瞭通信效率。

基於此,TCP通信雙方維護一個叫做擁塞窗口(cwnd,congesion window)的值,這個值取決於網絡中的擁塞率,發送方的發送窗口的值就等於擁塞窗口的大小。隻要網絡中沒有出現擁塞,擁塞窗口的值就可以增大一些,這樣發送方可以發送到網絡中的數據就多一些。反之,擁塞窗口的值就減小,從而避免加劇網絡的擁塞率。

TCP目前擁塞控制主要有以下4種算法:

1.慢啟動

2.擁塞避免

3.快速重傳

4.快恢復

具體的算法實現方式就不再介紹瞭,大概實現的功能就是,基於當前的網絡狀況,找到一個合適的發送速率,防止給網絡造成過大的負擔。比如說慢啟動,就是開始的時候,發送得較慢,然後根據丟包的情況,調整速率:如果不丟包,就加快發送速度;如果丟包,就降低發送速度。

六、TCP 狀態

瞭解TCP的都知道,TCP 建立連接的時候,有三次握手,斷開鏈接的時候又四次握手交互。那麼其中的狀態是有哪些?

上面的圖看著是不是太亂記不住,我們看看下面這張梳理一下,看看具體應用狀態。

從上面可以看到,連接建立成功的時候,其狀態是ESTABLISHED 的。當接受端的狀態為SYN—RECV的時候,表示接受端,已經回復第二次握手信息瞭,等待發送端再次確認。如果網絡中遭受到大量的SYN 攻擊,會存在大量的SYN_RECV 狀態。此時可以定位這些問題IP ,通過防火墻過濾就能解決大量的假連接問題。

七、消失的連接——TIME_WAIT

在網絡中,某一端主動關閉而沒有通過四次握手關閉,此時tcp已經建立的通道是否還在,多久會關閉?此時的TCP 狀態為TIME_WAIT ,可以想象,現實中經常出現這種狀況,多數的關閉連接都是主動關閉而非通過協商通信關閉。那麼此時關閉,若果再重連還能重連上之前的tcp 通道麼,還是需要重現創建。

任何TCP實現必須為MSL選擇一個值,默認是2分鐘或者30秒,TIME_WAIT默認是2倍的MSL,持續時間在1-4分鐘之間。MSL是IP數據包能在網絡中存活的最長時間。

TIME_WAIT 存在的兩個理由:

1、可靠的實現TCP全雙工連接的終止

2、允許老的重復分節在網絡中消失

TCP必須防止某個連接的老的重復分組在該連接已經終止後再現,從而被誤解成屬於同一連接的化身,有time_wait 足夠長,是2倍的MSL的,那麼足夠讓某個方向上的分組最多存活MSL秒就被丟棄。

從TIME_WAIT狀態到CLOSED狀態,有一個超時設置,這個超時設置是 2*MSL(RFC793定義瞭MSL為2分鐘,Linux設置成瞭30s),如此超過瞭這個時間,當前的tcp通道就會被定義為關閉。

以上就是淺析NIO系列之TCP的詳細內容,更多關於NIO TCP的資料請關註WalkonNet其它相關文章!

推薦閱讀: