計算機網絡總結(二)——TCP相關知識


2.TCP相關知識

2.1 TCP短連接和長連接

1.短連接:

Client 向 Server 發送消息,Server 回應 Client,然后一次讀寫就完成了,這時候雙方任何一個都可以發起 close 操作,不過一般都是 Client 先發起 close 操作。短連接一般只會在 Client/Server 間傳遞一次讀寫操作。

短連接的優點:管理起來比較簡單,建立存在的連接都是有用的連接,不需要額外的控制手段。

2.長連接:

Client 與 Server 完成一次讀寫之后,它們之間的連接並不會主動關閉,后續的讀寫操作會繼續使用這個連接。

在長連接的應用場景下,Client 端一般不會主動關閉它們之間的連接,Client 與 Server 之間的連接如果一直不關閉的話,隨着客戶端連接越來越多,Server 壓力也越來越大,這時候 Server 端需要采取一些策略,如關閉一些長時間沒有讀寫事件發生的連接,這樣可以避免一些惡意連接導致 Server 端服務受損;如果條件再允許可以以客戶端為顆粒度,限制每個客戶端的最大長連接數,從而避免某個客戶端連累后端的服務。

長連接和短連接的產生在於 Client 和 Server 采取的關閉策略,具體的應用場景采用具體的策略。

 

2.2 TCP重傳、滑動窗口、流量控制、擁塞控制

1. 重傳機制

TCP實現可靠傳輸的方式之一,是通過序列號與確認應答。但是在錯綜復雜的網絡環境中,很有可能出現數據包丟失的情況,如果數據包丟失了,TCP會用重傳機制解決。

(1)超時重傳

TCP在發送數據時,會設置一個定時器,如果一個已經發送的報文段在指定時間內沒有收到確認應答報文,那么就重傳這個報文段。一個報文段從發送再到接收到確認所經過的時間稱為往返時間 RTT,而超時時間稱為 RTO,RTO的值應略大於RTT,如圖所示:

(2)快速重傳

TCP還有一種快速重傳機制,它不以時間為驅動,而是以數據驅動重傳。

如果發送端連着收到了三個ACK標志位相同的確認,則發送端知道了對應的數據包接收端未收到,就會在定時器過期之前,重傳丟失的數據包。

(3)SACK

如果不知道該重傳哪些TCP報文,可以用SACK方法,這種方式需要在TCP頭部【選項】字段里增加“SACK”,將緩沖區的數據發送給對方,這樣發送方就知道哪些數據收到了,哪些沒收到。這時,就可以只重傳丟失的數據。

2. 滑動窗口

TCP因為每發送一個數據,都要進行一次確認應答,當上一個數據包收到應答了,再發送下一個。但是這樣效率非常低,所以TCP引入窗口的概念,窗口是緩存的一部分,用來暫時存放字節流。

窗口可以設置大小,窗口大小就是無需等待確認應答,而可以繼續發送數據的最大值。發送方和接收方各有一個窗口,接收方通過 TCP 報文段中的窗口字段告訴發送方自己的窗口大小,發送方根據這個值和其它信息設置自己的窗口大小。發送方發送的數據大小不能超過接收方的窗口大小。假設窗口大小為3個TCP段,那么發送方就可以連續發送3個TCP段,並且如果中途有ACK丟失,可以通過下一個確認應答進行確認。如圖所示:

發送方和接收方的窗口示例如下圖所示,其中發送方窗口分為4個部分,接收方窗口分為3個部分:

如果發送窗口左部的字節已經發送並且收到了確認,那么就將發送方窗口向右滑動一定距離,直到左部第一個字節不是已發送並且已確認的狀態,接收窗口的滑動類似,接收窗口左部字節已經發送確認並交付主機,就向右滑動接收窗口。

上圖中,假設發送窗口中的31~33字節的ACK確認應答后,如果發送窗口大小未發生變化,則發送窗口向右滑動3個字節,與之對應的可用窗口也向右滑動3個字節,那么后續可以發送41~43這3個字節的數據。類似的,接收窗口收到數據后,也向右滑動3個字節。

3. 流量控制

如果接收方處理不過來數據,而發送方又一直發送數據的話,就會觸發重傳機制,從而導致網絡流量的浪費。為了解決這種問題,TCP可以進行流量控制,讓發送方根據接收方的實際接收能力控制發送的數據量,避免發送方的數據填滿接收方的緩存。發送端主機會時不時的發送一個叫做窗口探測的數據段,此數據段僅包含一個字節來獲取最新的窗口大小信息。即TCP通過讓接收方指明希望從發送方接收的數據大小(窗口大小)來進行流量控制

發送窗口和接收窗口中所存放的字段,都是放在操作系統內存緩沖區的,而操作系統的緩沖區,會被操作系統調整。如果服務非常繁忙,操作系統可能會減少緩沖區的大小,如果此時應用程序沒有讀取數據,將收到的數據留在緩沖區中,會收縮窗口,就會出現丟包的現象。例如:發送數據大小超過了接收窗的大小,就會丟包。所以TCP規定不允許同時減少緩沖區又收縮窗口,只能先收縮窗口,過段時間再減少緩存

如果接收窗口大小為0,就會阻止發送方給接收方傳遞數據,知道窗口變為0位置,這就是窗口關閉。

4. 擁塞控制

計算機網絡時常會出現擁堵現象,這時如果發送方發送大量數據包,就可能會導致數據包時延,丟失等。這時TCP就會重傳數據,從而加重網絡負擔,形成惡性循環。所以當網絡發生擁塞時,TCP會進行擁塞控制。

擁塞控制是發送方維護的一個的狀態變量,它會根據網絡的擁塞程度動態變化。這一點和流量控制很像,但是出發點不同。流量控制是為了讓接收方能來得及接收,而擁塞控制是為了降低整個網絡的擁塞程度

引入擁塞控制后,發送窗口的值是 swnd = min(cwnd, rwnd),也就是擁塞窗口和接受窗口的最小值。只要網絡中沒出現擁塞,cwnd 就會增大,但網絡中出現了擁塞,cwnd就會減少。一般來說,如果發送方發送超時重傳,就會認為網絡中出現了擁塞

TCP 主要通過四個算法來進行擁塞控制:

慢開始、擁塞避免、快速重傳、快速恢復。

(1)慢開始與擁塞避免

TCP在剛建立連接完成后,首先是有個慢開始的過程,一點一點的提高發送數據包的數量。當發送方每收到一個ACK,擁塞窗口cwnd的大小就會加1。例如,開始時,令 cwnd = 1,發送方只能發送 1 個報文段;當收到確認后,將 cwnd 加倍,因此之后發送方能夠發送的報文段數量為:2、4、8 ... 。

這里設置一個慢開始門限 ssthresh,當 cwnd >= ssthresh 時,進入擁塞避免,當發送方每收到一個ACK,擁塞窗口cwnd的大小就會加1/cwnd,即每個輪次只將 cwnd 加 1。如果出現了超時,則令 ssthresh = cwnd / 2,然后重新執行慢開始。

(2)快速重傳和快速恢復

擁塞發生時,會用到前面討論過的重傳機制中的方法,即超時重傳和快速重傳。在接收方,要求每次接收到報文段都應該對最后一個已收到的有序報文段進行確認。例如已經接收到 M1 和 M2,此時收到 M4,應當發送對 M2 的確認。在發送方,如果收到三個重復確認,那么可以知道下一個報文段丟失,此時可能會發生超時重傳,重新進入慢開始,但是這樣會突然減少數據流,反應強烈容易造成網絡卡頓,更多的采用快速重傳的方式,立即重傳下一個報文段。例如收到三個 M2,則 M3 丟失,立即重傳 M3。

在快速重傳后,只是丟失個別報文段,而不是網絡擁塞。因此執行快速恢復,令 ssthresh = cwnd / 2 (也就是設置為原來的一半),cwnd = ssthresh,重傳丟失的數據包。如果收到新的ACK,說明ACK確認了新的數據,恢復過程已經結束,可以進入到之前的狀態,也就是擁塞避免狀態。如圖所示:

注意慢開始和快速恢復的快慢指的是 cwnd 的設定值。擁塞控制的整個過程如圖所示(圖中第2步為超時重傳,第3步為快速重傳):

 

2.3 TCP粘包、拆包問題及解決辦法

(1)TCP粘包、拆包的概念

TCP有粘包和拆包問題,而UDP沒有,UDP 是基於報文發送的,UDP首部采用了 16bit 來指示 UDP 數據報文的長度,因此在應用層能很好的將不同的數據報文區分開,從而避免粘包和拆包的問題。

TCP 是基於字節流的,雖然應用層和 TCP 傳輸層之間的數據交互是大小不等的數據塊,但是 TCP 並沒有把這些數據塊區分邊界,僅僅是一連串沒有結構的字節流;另外從 TCP 的幀結構也可以看出,在 TCP 的首部沒有表示數據長度的字段,基於上面兩點,在使用 TCP 傳輸數據時,才有粘包或者拆包現象發生的可能。

假設 Client 向 Server 連續發送了兩個數據包,用 packet1 和 packet2 來表示,那么服務端收到的數據可以分為三種情況,現列舉如下:

1)第一種情況,接收端正常收到兩個數據包,即沒有發生拆包和粘包的現象。

2)第二種情況,接收端只收到一個數據包,但是這一個數據包中包含了發送端發送的兩個數據包的信息,這種現象即為粘包。這種情況由於接收端不知道這兩個數據包的界限,所以對於接收端來說很難處理。

3)第三種情況,接收端收到了兩個數據包,但是這兩個數據包要么是不完整的,要么就是多出來一塊,這種情況即發生了拆包和粘包。這兩種情況如果不加特殊處理,對於接收端同樣是不好處理的。

(2)TCP 粘包、拆包發生原因

  • 要發送的數據大於 TCP 發送緩沖區剩余空間大小,將會發生拆包。

  • 待發送數據大於 MSS(最大報文長度),TCP 在傳輸前將進行拆包。

  • 要發送的數據小於 TCP 發送緩沖區的大小,TCP 將多次寫入緩沖區的數據一次發送出去,將會發生粘包。

  • 接收數據端的應用層沒有及時讀取接收緩沖區中的數據,將發生粘包。

(3)TCP粘包、拆包解決辦法

由於 TCP 本身是面向字節流的,無法理解上層的業務數據,所以在底層是無法保證數據包不被拆分和重組的,這個問題只能通過上層的應用協議棧設計來解決,根據業界的主流協議的解決方案,歸納如下:

  • 消息定長:發送端將每個數據包封裝為固定長度(不夠的可以通過補 0 填充),這樣接收端每次接收緩沖區中讀取固定長度的數據就自然而然的把每個數據包拆分開來。

  • 設置消息邊界:服務端從網絡流中按消息邊界分離出消息內容。在包尾增加回車換行符進行分割,例如 FTP 協議。

  • 將消息分為消息頭和消息體:消息頭中包含表示消息總長度(或者消息體長度)的字段。

  • 更復雜的應用層協議比如 Netty 中實現的一些協議都對粘包、拆包做了很好的處理。

 

參考:

  1. 《TCP-IP網絡編程》 韓-尹聖雨
  2. 《圖解網絡》小林coding

  3. 《圖解TCP/IP》

  4. https://zhuanlan.zhihu.com/p/108822858

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM