計算機網絡——TCP如何做到可靠數據傳輸


一、前言

  這幾天寫了四篇TCP系列的博客,這一篇是第五篇,也預計是這段時間的最后一篇了,寫完這篇我就要開始進行網絡層的研究了。若對於我其他TCP方面的博客感興趣,可以去我個人博客的計算機網絡這一分類中查閱。這篇博客就來談一談TCP是通過哪些手段,來保證可靠數據傳輸的。


二、正文

 2.1 網絡傳輸存在的問題

  研究TCP如何保證可靠數據傳輸之前,我們先來列舉一下網絡傳輸存在什么問題,只有發現了問題,才能對症下葯,找出應對的方法。TCP是依靠網絡層的IP協議來發送數據,而IP協議是一個不可靠的協議,它僅僅只是盡最大努力傳輸,但是並不保證數據能夠完好地到達,甚至不能保證數據能否到達。同時,網絡中允許傳輸的最大單元(MTU)是有限制的,一般為1500個字節。所以,TCP為了發送比這個大的數據,需要將數據拆分成一個個數據段進行發送。正因為這些原因,網絡傳輸將存在以下問題:

  1. 數據在傳輸的過程中被損壞,比特的0變成1,或者1變成0
  2. 數據在傳輸過程中丟失,沒有到達目的地;
  3. 多個報文段沒有按順序到達接收方,因此接收方無法正確地將數據組合;

  TCP的實現基本上就是圍繞上面三個問題,以及如何提高傳輸速率實現的。下面我們就來說一說TCP為了應對上面這些問題,做了哪些事情。


 2.2 TCP解決數據損壞

  我們先來討論第一個問題,數據發生損壞。檢驗數據是否損壞的方式就是數據校驗,以下是TCP報文的格式,可以看到其中有一個16位的字段,叫做校驗和,這就是接收方用來校驗數據是否出錯的部分。

  這個字段的名稱叫做校驗和,因為TCP對數據校驗的方式就是校驗和算法,這個算法的過程如下:

  1. 將校驗和字段置為0,然后將數據部分以16位為一個單位,進行拆分;
  2. 將拆分后的若干個單元進行二進制相加,若相加結果進位到了第17位,則將17位加到第一位(其實就是補碼和運算,也稱為回卷),最終相加的結果取反(0變成11變成0),然后放入校驗和字段;
  3. 報文段被發送到目的地后,目標主機也進行上面兩步,然后將運算的結果與首部中的校驗和字段相加,再取反,若結果為0,則認為數據沒有出錯;

  這樣是如何起到校驗的作用呢?其實很簡單,假設在數據傳輸的過程中,數據沒有出錯,則在發送端和接收端,求出的結果都是一樣的。而校驗和字段,就是這個結果的反碼,也就是說,結果中為1的位,在反碼中就為0,而結果中為0的位,在反碼中就是1。也就是說,在數據沒有出錯的情況下,這個結果與校驗和字段相加,一定是全1,而此時再取反,就是0了。所以只要最終求得的結果不是0,接收方就認為數據出錯。若數據出錯,則接收方會直接將數據丟棄,發送方在一段時間后,沒有接收到ACK報文,就會在超時后,重傳這個報文

  但是,上面這個算法一定可以校驗出數據是否出錯嗎?答案是不能。因為這個校驗的根本,其實不過是將數據求和,然后判斷這個和有沒有改變而已。而我們都知道,1+2 == 2+11 + 4 == 2 + 3,僅僅依靠求和,根本無法保證數據的沒有發生改變。只要數據的多處都發生了錯誤,而且相互抵消,這種算法將無法察覺。但是TCP依然采用了這種算法,我個人認為原因是實現簡單,而且網絡中數據出錯的概率不高,而多處出錯並相互抵消的概率就更小了,所以這種算法的可靠性還是比較高的。


 2.3 TCP解決丟包問題

  TCP解決數據丟失的方法就是超時重傳TCP將維護一個計時器,並設置一個超時時間,當發送一個TCP報文段后,沒有在超時時間之內得到ACK報文,則發送方將認為數據丟失,於是將重傳丟失的報文段,直至接收。由於TCP使用的是流水線傳輸,在同一時間內,可能有多個已經發送但沒有接收到ACK的報文段,所以按理來講,TCP將維護多個計時器,為每一個報文段綁定一個,但是這樣做將產生較大的開銷,而且對於計時器的管理也很復雜。所以在TCP中,實際上只會維護一個計時器,記錄的是當前最早被發送,但是還沒有接收到ACK報文的報文段。當這個報文段超時,發送方將重傳報文段,並重啟計時器;若收到這個報文段的ACK報文,同樣重啟計時器,不過此時最早被發出但還沒有被確認的報文段已經發生了改變,此時記錄的就是這個新的報文段的傳輸時間。除此之外,觸發快速重傳時,計時器也會重新啟動。關於TCP的流水線傳輸,可以參考我的這一篇博客:https://www.cnblogs.com/tuyang1129/p/12450978.html

  這里就有一個復雜的問題了,這個超時時間將要如何設定?不難想到,這個超時時間應該要略大於數據的往返時間(RTT),比如數據從發送到接收到ACK報文,共用了200ms,那超時時間應該略大於這個值,比如400ms。但是網絡是不穩定的,對於每一個報文,因為所通過的路徑不同,網絡擁塞程度的不同,往返時間或多或少都會發生改變。所以對於這個超時時間,應該基於平均RTT進行計算。但是直接統計求平均值就太粗糙了,對於TCP來說,有一套復雜的算法來計算這個超時時間。

  要計算RTT的大致平均值,首先得有樣本值,假設樣本RTT定義為SampleRTTTCP程序在運行期間,可能會在任意時刻測量一次SampleRTT,即測量一個報文從發出到接收ACK所用的時間,然后將它用於計算RTT的加權平均值。然后過一段時間再進行一次,並將新測出的SampleRTT用於更新加權平均值。假設這個加權平均值RTT定義為EstimatedRTT,在TCP規范中,計算EstimatedRTT的公式為:

EstimatedRTT = (1- α)*EstimatedRTT + α * SampleRTT(公式一)

  其中SampleRTT就是最新測得的樣本RTT,通過以上公式就能動態地確定RTT的加權平均值。由於越晚測量的SampleRTT,越接近網絡中當前的狀況,所以在更新EstimatedRTT 的過程中,最新的SampleRTT應該要占據更多的比重,所以在TCP規范中,建議將α的值設定為1/8,所以上面的公式就是:

EstimatedRTT = 0.875 * EstimatedRTT + 0.125 * SampleRTT

  而SampleRTTEstimatedRTT的波動圖如下所示:

  除了求RTT的加權平均值,網絡中RTT的變化情況也是很有必要的,畢竟從上圖可以看出,樣本RTT的波動十分劇烈,只有EstimatedRTT,還不足以讓我們准確的估算超時時間。所以我們需要求出SampleRTTEstimatedRTT的偏離程度,也就是類似於方差,根據方差,來動態地設置超時時間。假設這個方差定義為DevRTT,則TCP規范中定義DevRTT的計算公式如下:

DevRTT = (1 - β)* DevRTT + β * | SampleRTT - EstimatedRTT |(公式二)

  由上面公式可以看出,如果SampleRTT 的波動很大,DevRTT 的值就會很大,反之就會很小。而在TCP規范中β的推薦值是0.25。我們現在知道了RTT的加權平均值,也知道了RTT的波動情況,現在就該考慮怎么設置超時時間了。不難想到,超時時間應該需要比RTT的加權平均值,也就是EstimatedRTT 要大一些,讓大部分報文段的RTT都小於這個值,以免頻繁超時重傳。那應該大多少呢?這樣考慮,當網絡波動較為劇烈時,表示實際RTT應該會離EstimatedRTT遠一些,而波動較小時,實際RTT應該會接近於EstimatedRTT,而這個波動情況的數值,我們已經計算過了,就是上一個公式中的DevRTT ,所以假設超時時間定義為TimeoutIntervalTCP規范推薦使用以下方式來計算它的值:

TimeoutInterval = EstimatedRTT + 4 * DevRTT (公式三)

  這樣,不論是加權平均值還是網絡的波動情況就都考慮到了。而TCP規范中推薦的初始TimeoutInterval 1s(當時從書上看到這部分內容,才深刻體會到了數學的強大,真真的將理論應用於實際)。當然,對於超時時間的計算,還有兩個特例:

  • 當某個報文段超時,發送方將重傳這個報文段,同時重新開啟定時器,而超時時間將會設置為上一次的兩倍,而不是使用公式三計算的值;若還是超時,則繼續重傳,超時時間再次擴大兩倍,直到這個報文被成功接收,而成功接收時,才使用公式三重新計算超時時間。這么做的目的是為了防止多次超時導致連續重傳,從而導致網絡擁塞更加嚴重,畢竟超時就是網絡擁塞的結果。
  • 記錄樣本RTT時,不會選擇重傳的報文段作為樣本,這是因為,當發生超時事件時,發送方並不知道數據是因為丟失還是因為網絡延遲而超時。若報文段是因為延遲而超時,則重傳報文后,這時延遲的ACK報文到達,發送方將誤以為重傳的分組被正確接收,於是將測出一個錯誤的SampleRTT

  總之,TCP的超時重傳機制,很好地解決了網絡中發生數據丟包的問題。而且,為了提高效率,TCP還有一種快速重傳機制,可以根據特定情況,在超時前就判斷報文段丟失,然后進行重傳,不過這里就不詳細敘述了。


 2.4 TCP如何解決數據亂序到達

  第三個問題就是數據的亂序到達問題。由於網絡的限制,TCP必須將較大的數據拆分成一個個較小的報文段,封裝成TCP報文段,逐個傳輸。由於網絡傳輸的不確定性(比如所通過的路徑不同,某個報文段丟失然后重傳等),這些報文段完全有可能不是按照順序到達。所以,為了在接收方能夠完整的接收數據,並能按序將這些報文組合起來,TCP必須有一種機制解決這個問題。

  TCP所使用的方法就是為每一個TCP報文段分配一個序號,每個報文段的序號依次增加,這樣接收方就可以根據序號,來確定接收到的報文段是整個數據中的哪一部分,以及是否接收到了所有的部分。從上面那張TCP報文結構圖中我們可以看到,其中有一個32位的序號字段。但是,TCP對報文段的編號可不是0,1,2,3....這么簡單,下面我們就來說說TCP是如何實現這種序號機制的。

  首先我們要明確一個點,TCP是對字節進行編號,而不是對報文段進行編號TCP對需要發送的數據的每一個字節都賦予了一個編號,比如第一個字節為0號,第二個為1號,以此類推。而每一個報文段一般都不止封裝一個字節的數據,所以在TCP報文段中,封裝的是這個報文段的數據中,第一個字節的序號。舉個例子,比如說發送方要發送250字節的數據,假設初始序號從0開始,則這250個字節的序號分別是0-249。再假設每一個報文段最多允許封裝100個字節的數據,所以第一個報文段將封裝第1100個字節,這些字節的序號為0-99,所以第一個報文段會將0放入它首部中的序號部分;而第二個報文段封裝100-199號字節,所以它的序號為100;而第三個報文段封裝200-249號字節,所以它的序號為200。以上就是TCP發送方對序號的處理方法。

  下面來說一說TCP的接收方如何在這種序號機制中工作。同樣以上面三個報文段舉例,假設發送方將上面三個報文段發出,接收方接收到第一個報文段,發現這個報文的序號是0,同時包含100個字節的數據,於是接收方將會向發送方確認已經接收到這個報文,而確認的方式就是使用TCP首部中的確認序號字段。接收方接收到序號為0,長度為100個字節的報文段后,將會在ACK報文的確認序號中填入100,表示自己已經接收到序號小於100的全部字節,希望下一個接收到的報文段的序號是100;而第二個報文段按序到達,序號為100,長度為100字節,於是接收方再次回送ACK報文,此時確認序號將為200,表示自己接收到200以前的全部字節,希望下一條報文的序號是200;然后接收到序號為200,長度為50字節的報文段,將回送確認序號為250的ACK報文。

  以上是按順序接收到報文段的情況,假設在上面的情況中,三條報文到達的先后順序是0 -> 200 -> 100,也就是順序被打亂,則將發生以下情況:

  1. 接收方接收到序號0,長度100字節的報文段,回送確認號為100ACK報文;
  2. 接收到序號200,長度為50字節 的報文段,此時接收方希望接收到的是100號報文,於是判斷發生了亂序到達的情況,不向上層交付這一段數據,而是將其放入接收緩存;
  3. 接收到序號為100,長度為100的報文段,100正是接收方期待接收到的報文段序號,於是將其接收並交付給上層,同時發現在接收緩存中存在序號為200的報文段,這正是接收方期待接收到的下一條報文,於是將其取出,交付上層,同時向發送方發送ACK報文,ACK的確認序號為250,表示自己已經接收到250之前的所有字節,下一條期望到達的報文的序號是250

  通過上面的機制,接收方成功解決了數據亂序到達的問題。當然,對於這種序號機制的使用,其實不止這么簡單,這其中還牽涉到TCP的流水線傳輸機制,若想要了解,可以參考我的另外一篇博客——https://www.cnblogs.com/tuyang1129/p/12450978.html

  這里還有一個問題,在上面的例子中,我假設序號是從0開始,但是實際情況並非如此。在實際的實現中,序號一般是一個通過特殊算法計算出的隨機值,這樣做的原因有兩點:

  1. 假設每一個TCP連接的序號都是從0開始,那么假設客戶端先向服務器發送了一個報文,還沒有確認接收后,立即斷開連接;但是在斷開后,它們立刻又建立了一個連接,而此時,第一次發送出去的報文段才剛到達服務器,那會發生什么情況。服務器會以為這是新建立的連接發送的數據,而由於兩次連接的初始序號都是0,接收方將會把這個報文段接收。為了減小類似情況發生的概率,TCP采用隨機初始序號,這樣兩次連接的初始序號將大概率不同,再發生這種情況時,接收方也不會接收這個報文段;
  2. 第二個原因就是出於安全性考慮,若初始序號都是固定的,那每一個報文段的序號完全可以推測得出,於是就有黑客可以利用這一點,模擬發送方發送TCP報文,做出攻擊,比如發送大量連接請求,占用服務器資源;

 2.5 TCP的流量控制與擁塞控制

  流量控制與擁塞控制,嚴格來講並不是TCP的可靠傳輸機制,但是也算是有點關系,所以我還是提一下。

  • 流量控制:TCP的接收方會維持一個接收緩存,用以接收發送方發送的數據。但是,接收緩存不是無限大的,若接收緩存被占滿,此時再接收到數據,將無法進行接收,只能將其丟棄。於是,為了減少這種情況的發生,TCP接收方需要告知發送方,自己最多還能接收多少數據,TCP發送方根據這個信息,有選擇的發送數據,這就是流量控制;
  • 擁塞控制:和流量控制類似,但是限制發送數據多少的不是接收方,而是路由器。路由器也有接收緩存,若路由器的接收緩存中存在過多的數據,也會對網絡傳輸造成影響,這就是網絡中丟包的原因,而擁塞控制就是根據網絡的擁塞狀況控制發送數據的速度;

  這兩種機制中,流量控制相對簡單。我們可以看到,在TCP的報文格式中,有一個叫做窗口大小的部分,這部分就是接收方告訴發送方,自己當前最多還能接收多少數據,而發送方將發送小於這個窗口大小的數據長度。但是有一種特殊情況,若這個窗口大小為0,表示當前窗口已滿,正常情況下發送方將無法發送數據,但是在實際情況中,發送方還是會發送一個字節的數據到接收方,作為一種試探。因為接收方一般不會主動向發送方發送報文,這個窗口大小一般是攜帶在ACK報文中,若此時窗口大小為0,發送方將不再發送數據,接收方也就無法向發送方發送ACK報文,此時就算緩存被清理,發送方也不會知曉。所以即使這個窗口大小為0,發送方仍然需要發送數據,進行試探,若緩存已經被清理,通過試探報文的ACK報文,發送方就能知曉。

  擁塞控制是TCP中相對復雜的一種機制,不是三言兩語說的清楚的,這部分內容我專門寫了一篇的博客進行說明,感興趣的可以閱讀一下:https://www.cnblogs.com/tuyang1129/p/12439862.html


三、總結

  對於TCP可靠傳輸的描述就介紹到這里。上面的內容是對TCP可靠傳輸原理的基本介紹,但是具體實現可能會在這些基礎上進行改進和優化。TCP的各種機制相輔相成,若是對於TCP沒有太多了解,可能有些介紹會看不太懂,所以若想真正搞懂TCP以及其他計算機網絡的相關知識,建議買一本書系統地研究。希望我的這篇博客對看到的人有所幫助,若博客內容有誤,希望可以指正。


四、參考

  《計算機網絡——自頂向下方法(原書第七版)》


免責聲明!

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



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