--- creator: Zach tags: 計算機網路, socket, TCP created: 2021-10-08 --- # 網路是怎樣連接的(八)TCP的性能優化(上) ## 思考重點 - TCP具有那些性能優化機制? - 滑動窗口的特色? - 滑動窗口發生丟包怎麼辦? ## 核心知識 ![](https://i.imgur.com/H4wpz9K.png) <center>核心知識點</center> ## 一系列的優化機制 起初的TCP採用一問一答模式,也就是說發送方一定要等到接收方返回ACK消息才能進行下一個封包的發送,撇除逾時不說,在等待的過程中等於發送方什麼事也不能做,白白浪費了這一段時間。 為了解決這個問題,發明了滑動視窗控制,使得在等待的過程中也可以持續發送封包消息,並且滑動視窗擁有比逾時重傳更高效的高速重送機制,大大提升了效率問題 > 實現滑動視窗目的: 提高通訊效率問題 但問題來了,這麼高密度的傳送封包,接收方可以承受得住嗎?其實接收方有配備一個專門用來接收封包的緩衝區,若是發送來的封包來不及處理,可以先暫時儲存到緩衝區中,等待之後應用程式來撈數據。問題看似解決了,其實沒有。 當發送方的封包到達速率遠遠大於接收方處理封包的速率,就會造成緩衝區**溢出**問題,因此需要引入流量控制,還記得前面提到的視窗大小嗎?引入流量控制會依據接收方緩衝區當前剩餘的容量來告知發送方發送封包的限制大小 >實現流量控制目的: 依照接收方緩衝區大小調整封包的視窗大小 既然我們實現了高效的通訊機制以及即時調整視窗大小的功能,大抵上應該沒有任何問題了吧? 請大家不要忽略了封包現實傳送中的狀況,以上兩點的判斷背景都是假設封包在傳送路途中沒有歷經各種網路塞車問題,換言之就是將封包往返時間看成一個不變的定值,也可以說是完美的狀態。 但在現實場景,網路的連線品質是一直在變動的。由於這個問題,我們不得不考慮接收方計算出來的視窗大小是否符合當前網路的連線能力,有點像高速公路目前只能容忍10台車,這時候你硬要派20台車上路,結果可想而知 >實現壅塞控制目的: 依照真實情景調整允許的視窗大小,若視窗大小超過上限,則以上限為主 ### 視窗控制 ![](https://i.imgur.com/fubz9IB.png) <center>視窗控制的效率優勢</center> </BR> 視窗控制的目的就是為了提高單位時間內發送封包的效率,而視窗控制使用了[視窗大小](https://hackmd.io/@Zacch/B1RjRp7EK)這個概念,由接收方判斷當前緩衝區可以儲存多長的資料,再透過ACK告知發送方。右圖中假設視窗大小為4000bytes,因此MSS長度為1000bytes的封包可以連續發送四次,這種有別於一般一問一答式的收發模式稱為**滑動窗口** 我們假設要傳送的TCP封包總長度10000bytes,從圖中可以看出單位時間內兩者之間的差別,不過一問一答只要負責當前的封包的收發確認即可,因此需要用來備份的內存記憶體也較小,整體行為上也較簡單。滑動視窗提升了單位時間的效率,但也需要更多的內存記憶體來儲存,不過因為現代存儲技術的發達,這些記憶體花費幾乎可以忽略不計,因此整體上滑動窗口的CP值更高 </BR> ![](https://i.imgur.com/3DO2sJw.png) <center>用社交軟體來比喻兩種收發模式</center> </BR> >你可以想像通訊軟體因為訊號問題,一分鐘內可以傳4條訊息,每條訊息的長度也有限制,如何在最快的時間告知對方我們的想法?不過我個人是不太喜歡第二種聊天方式,哈哈哈 #### 滑動窗口 視窗大小的觀念很有趣,它不用等待**個別的**ACK確認回應才進行發送,而是依照接收方設定的視窗大小依序將好幾筆封包發向接收方,也就是說在滑動窗口模式下,能決定傳送幾筆封包是由**接收方**決定 我們在TCP頭部有介紹過視窗大小的控制位(Window),它表示能傳送的資料大小,傳送的封包資料量不能超過視窗大小,但是若視窗大小為0,可以允許發送*視窗探索*封包來了解接收方視窗當前狀況 為了要了解滑動窗口,我們必須要扒開作業系統內存,看看它葫蘆裡到底賣甚麼藥 ![](https://i.imgur.com/okJUomr.png) <center>滑動窗口內存</center> </BR> 作業系統為了確保發送的封包確實的被接收方收到,同時實現視窗控制的目的,必須限制出一個相當於視窗大小的資料長度來指定發送並且備份封包資料,我們先以發送方為例介紹一下緩衝區中每個區塊代表的意義 **綠色區塊** 就是已經接收ACK響應的封包,這部分的記憶體將會被釋放掉(因為已經沒有重傳的必要了)。例如在*視窗控制的效率優勢*一圖中返回包含序號1001的ACK消息,就會將當初發送的1~1000這個封包的資料釋放 **黃色區塊** 表示已經發送但還未取得響應的封包,假如發生逾時或者丟包狀況,主要是重傳這個區塊。在*視窗控制的效率優勢*圖中5001~6000這個封包尚未接收到接收方的ACK響應 **紅色區塊** 代表在視窗範圍大小內即將要發送的封包。我們一樣來看看*視窗控制的效率優勢*一圖,當我們的控制流程處於第一次發布的狀態時(發送資料1\~4000),當作業系統正在處理並發送1~1000這個封包時,剩餘的1001\~2000、2001\~3000、3001\~4000都是處在視窗範圍內但是尚未被發送的狀態 **紫色的區塊** 代表不在當前視窗大小範圍內。不說了還是看同一張圖,假設發送方接收到接收方5001的ACK響應,發送方作業系統會主動釋放出記憶體,而多出來的這塊記憶體就會讓整個處理視窗向右移向原本不再視窗大小範圍內的記憶體位置(紫色區塊),也就是8001~9000這個封包,所以紫色區塊有可能是尚未處理的資料,也有可能是別的記憶體區塊 不知道大家有沒有看過工廠的輸送帶,就是流水線上一排作業員會將輸送過來的物品進行處理那種?阿呀,真的有點難解釋,我自己喜歡用這種方式來理解滑動視窗內存控制,還是上圖吧 ##### 發送方 ![](https://i.imgur.com/OYkk98k.png) <center>發送方內存 </center> </BR> 這張圖怎麼解釋呢?首先一個作業員可以處理特定長度的數據包,我們在這裡假設這個工廠有個不成文規定,當最左邊的作業員完成工作後輸送帶就會啟動,把整個數據包往左移,四個作業員就相當於當前視窗大小,要處理的資料數據總長為10個,而最右邊的那位仁兄相當於作業系統 當封包在流水線上移動時,作業員分別對這些資料做處理,從圖一可以看出作業員一、二已經將數據處理完成,並等待品管燈檢查完畢,這相當於發送方將封包發出,等待接收方回傳ACK消息,由此可知數據包1, 2處於等待回應狀態,若是處理有問題將會要求重作,此外作業員三、四正在處理數據包的過程中 接下來所有作業員都完成所有工作,等待檢查燈亮起。當作業員一、二的數據包確定驗證成功,表示依序收到來自接收方的ACK響應,作業系統就會下令轉動輸送帶,並把這些數據釋出,並且轉動輸送帶會將作業員三、四的數據包交給作業員一、二,同時將本來未包含再視窗大小內的數據包5, 6交給作業員三、四處理 ![](https://i.imgur.com/hht491T.png) <center>滑動視窗方式 </center> </BR> 視窗內的資料封包初始時不需要獲得ACK就可以發送,但後續視窗的移動就需要使用響應來改變視窗左指標位置。上圖顯示接收到兩個ACK響應後,原先預留給封包2001\~3000、3001\~4000的記憶體就會被釋放,並將左指標向後移動兩個封包長度,同時作業系統為了保持視窗的大小,會將右指標也向未使用記憶體區段(包含在總長內)移動兩個封包長度,這種行為看起來就像是把視窗向右滑動一樣,所以稱之為滑動視窗控制 ##### 接收方 ![](https://i.imgur.com/8ajo3UM.png) <center>接收方內存 </center> </BR> 接收方的內存控制就相對來的簡單了,作業員一受委託就會將已經被確認過後的封包數據交給應用程式,而作業員二則在清點剛剛送達的封包數據,他一次只能驗證5個數據封包,這是他的最大處理數量,也就相當於視窗大小。確認成功後會把它們移交給作業員一等待應用程式調用。如上圖所示,作業員二確認完數據封包5後,就可以允許發送方發送數據封包10的消息了 ![](https://i.imgur.com/D7Z8CfV.png) <center>滑動視窗方式 </center> </BR> 當接收方確認收到2001\~3000、3001\~4000這兩個封包後,作業系統便會將指向視窗開頭的指標往左移位(從2001指向4001)最終整個視窗會向右移動。新的視窗大小含意是當前尚未接收到的數據,但可以接受的序號範圍,因為視窗移動的關係,增加了6001\~7000、7001\~8000兩個封包的允許接收權 #### 響應丟失 假設我們考慮封包丟失的狀況,滑動視窗是怎麼處理的呢?其實在滑動窗口模式下即使丟失響應也不需要重傳,而是可以直接利用下一個接收到的響應進行確認,起初我也是很不了解這點,既然可以忽略不計,那作業系統記憶體要怎麼知道這個記憶體區塊是否需要被釋放? 其實響應的丟失場景有兩種,假設是響應的封包搞丟了那還好,畢竟封包已經被接收了,就沒有重傳的必要,只是發送方不知道而已,但如果狀況是發送方的封包壓根就沒有發送出去怎麼辦?其實這個問題在下一小節*高速重傳中*會介紹,接收方會立即返回三個預期序號的響應,也就是說若只是封包丟失直接收到下一個響應,就代表發生的是場景一,其實封包已經被接收了 ![](https://i.imgur.com/YFuSpzi.png) <center>響應丟失場景 </center> </BR> 如上圖所示,我們假設視窗大小為4000,資料總長為10000,當接收到響應4001時,之前遺失的2001、3001響應就會自動被作業系統確認回應,隨即代表這兩個資料封包的記憶體區塊也會被釋放,還是放圖好理解 ![](https://i.imgur.com/aMQO1ut.png) <center>記憶體區塊的處理 </center> </BR> 當發送方接收到接收方的第一包響應,也就是2001時,作業系統隨即將備份的記憶體區塊釋放,於是視窗向右邊移一位並且發送4001\~5000這個封包。後續因為還沒有接收到任何的響應消息,為了保持視窗的大小頂多只發送到4001\~5000。最後發送方接收到5001響應,意思是1\~5000的資料都確實地被接收方接收,因此作業系統將1001~4001三個封包的記憶體區塊釋放 #### 高速重傳機制 視窗控制有搭配比逾時重傳更加有效率的*高速重傳機制*,它不是依據返回響應的時間來觸發,而是接收方依據預期的序號來檢視封包是否發生丟失,首先說到效率,不免俗的要跟於時重傳機制來比較一下,一樣我們將視窗大小設定成4000,資料總長設為10000 ![](https://i.imgur.com/512VrjN.png) <center>高速重傳機制的差別 </center> </BR> 當接收方收到與預期序號不一樣的封包時會立即在接下來返回響應時回傳三次應當收到的封包序號,對發送方來說,若連續收到三次同一個響應請求重傳,則會觸發高速重傳機制 從圖中我們可以看出使用高速重傳比逾時機制來的高效,在這種狀況下,若是使用逾時重傳,整個封包的發送就會卡在等待1~1000這個封包的響應後才會繼續,但在高速重傳過程中,**發送方還是會繼續發送符合視窗大小的資料**,雖然說接收方會對這些封包進行三次的重傳請求,但同時**接收方還是會將這些資料存進緩衝區內**,也就是默認接收了後來的這些封包,所以到最後接收方真正接收到丟失的封包時,它返回的是**目前接收到的最新封包ACK** > **為什麼高速重傳需要花費三次ACK才會觸發?** > 因為接收方不確定接收到的回應是網路傳輸問題導致的丟包還是純粹的亂序封包,兩次很有可能是亂序封包,三次有很高機率是丟包造成,四次、五次、六次有更高的機率是丟包造成,為了最大化提升效率,取三次作為高速重傳次數限制 </br> ![](https://i.imgur.com/eADF2N5.png) <center>發送方與接收方緩衝區窗口滑動 </center> </BR> 接下來看看記憶體緩衝區的視窗移動,首先發送方依序將封包1\~4000發出,緩衝區處立馬就處於等待回應階段,這很好理解吧,但因為不明原因導致封包2001~3000在傳輸過程中丟失了 當接收方接受到非預期封包時,首先會將該亂序封包儲存至對應緩衝區內。然後接收方會藉由跳回重傳1001~2000這個封包響應 (2001),來讓發送方知道要重傳2001~3000 封包。另外由於前兩個封包皆成功接收,接收方會依序將ACK消息返回給發送方,同時移動自身視窗,而發送方也會因為響應釋放前兩個區段的記憶體,並將視窗向右移動,同時空出的位置又可以進行新封包的發布 接收方依序接收到回應的兩個封包,但因為封包2001\~3000尚未確認,因此整個視窗會被卡住,導致視窗無法繼續向右拓展,唯一方法是接收到2001\~3000的確認響應 當發送方獲得三次高速回傳的響應時,會立即對遺失的封包進行重傳,接收方在確認丟失的封包回傳後返回之前收到封包的最長位置,因此會依據視窗最新的臨界指標進行ACK響應,還記得我們在響應丟失一小節說到滑動視窗會依照最新的響應序號來更新整個視窗都接收確認嗎?所以連同2001~3000在內的所有封包接被確認並釋放,到此為止高速重傳完成它的任務