《TCP/IP協議 詳解》思考總結 · TCP下篇

前言
這篇文章是整個讀書總結系列的最後一篇,有關TCP我想總結的內容都會在這篇文章結束。當然這並不是TCP的全部,總共的五篇文章都只是計算機網絡的基礎。枯燥而又繁雜的知識點只是進入網絡領域的入場券,學會理解了基礎纔可能繼續往下深耕。

作爲上篇的承接,下篇我們開始着手認識TCP的擁塞避免策略。對於傳統的TCP而言,擁塞的判斷完全依賴於丟包的情況,這樣判斷的理由是基於對現實網絡情況的總結經驗。我們會考慮超時和收到重複ack兩種情況的擁塞,並對於它們的不同做出不同的決斷。之後會簡單介紹一個TCP連接上的四個定時器。文章的最後我們簡單討論了TCP的現在和未來。

說實話我並不認爲TCP這些具體的操作內容對於閱讀者有任何的意義,因爲這些書本理論的東西和實際存在了非常大的鴻溝。我希望的是,能在介紹這些內容的過程中,和讀者分享一個感受:TCP做爲一個工程化的東西,受限於技術、硬件等等條件,設計的過程是充滿推斷和妥協的。如何使用有限的資源實現一個理論上不可能的需求,TCP給我們上了非常好的一課。

《TCP/IP協議 卷一》後面的章節還介紹了一些其他的內容,主要是幾個應用層的協議。可以簡單看一看了解一下,需要時再去參考。不再專門討論總結。

TCP的擁塞避免
TCP提供了可靠的傳輸服務。其中一個很重要的策略就是接收端必須返回ack,用以讓發送端確認數據抵達。但這個策略的問題在於,不可靠的信道上發送的數據報和返回的ack都有可能丟失,爲了解決這個問題,發送數據時,TCP會在發送端設置一個定時器用以檢查ack的返回。如果定時器溢出時還沒有收到接收端的ack,那麼發送端就會重傳這部分的數據。

TCP發送數據的ack和兩軍問題不同在於,數據正確送達只需要發送端確認即可。所以問題變的簡單很多,只需要一次單向握手就可以解決。

要實現一個高效的傳輸服務,關鍵就在於超時和重傳策略的確定。簡單來說,選擇一個合適的超時間隔和超時如何繼續重傳是非常重要的。

首先讓我們看一看超時時間間隔的選擇。在TCP當中有一個非常重要的概念:RTT(往返時間Round-Trip Time)。在計算機網絡中它是一個重要的性能指標。

什麼是RTT
摘取一段教材的定義

“We define the round-trip time, which is the time it takes for a small packet to travel from client to server and back to the client.”

“The RTT includes packet-progation delays, packet-queuing delays and packet -processing delay.”

RTT指代的是從發送端發送數據開始,接收端收到數據立即確認,到發送端收到來自接收端確認往返雙向的時延。通常情況下一個單向的時延我們是這樣計算的。

單向時延 = 傳輸時延(t1) + 傳播時延(t2) + 排隊時延(t3)

t1指數據從進入節點到傳輸媒體所需的時間,通常等於 數據塊長度/信道帶寬

t2指信號在信道上傳播所需的時間,通常等於 信道長度/傳播速率

t3通常受每一跳設備及收發兩端負荷情況還有吞吐情況影響

上述公式中可以看出,RTT的大小受多個因素影響。假設在同一個連接上t1可以認爲保持不變,但路由器和網絡流量的不確定,導致RTT的大小是會經常發生變化的。爲了解決這樣一個問題,TCP會跟蹤這些變化並不斷調整RTT,以此爲根據來設置相應的超時間隔。

爲了方便理解,先舉一個簡單的例子:

作者讀高中的時候每天騎車往返與學校和家,假設往返一趟的時間是20min。每天放學的時間是5:40 pm,那麼在六點之前如果我還沒有回家,父母就會電話老師詢問是否學校有留堂。

參考上面的內容,父母會根據我往返學校的時間得出我大概的回家時間,如果我沒有按時回來,那麼大概率的情況是有意外情況的發生。

對比參考RTT,指代的就是我往返學校的時間,父母就是發送端。TCP會根據RTT來決定超時間隔,判斷髮送數據報的ack大概的返回時間,從而設置定時器。如果定時器溢出發送端仍然沒有收到ack,那麼我們有理由認爲數據報在傳輸的過程中丟失,需要進行重傳。

本文不介紹RTT測量相關的內容,有興趣的朋友可以自行查閱

這裏需要強調的是,對於發送端而言,報文被傳送出去之後發生的事情,實際都是無處可知的。TCP很多時候採取的策略都是基於已有的部分信息,結合實驗得出的普遍結論來推斷得到的。結合重傳策略來說,在定時器溢出時,發送端實際並不知道數據報的實際情況。但是TCP會以此作爲信道擁塞的判定標準,認爲信道發生擁塞需要處理。

信道擁塞這個結論的前提是數據報的丟失是因爲擁塞而不是分組數據受到損壞(一般來說損壞導致的丟失遠小於1%),獲取的RTT是相對準確的。試想,如果前提條件不再成立,那麼信道擁塞的結論是否還值得相信呢?

在網絡不太穩定的場景(比如無線網絡)中,無線網絡因爲短暫的信號干擾會導致的丟包。但傳統的TCP流控算法會認爲發生擁塞從而指數避讓,導致速率大幅下降。很顯然在TCP設計之初並沒有考慮到這樣的情況。

實際這也是一個必然。工作中的經歷時常讓我思考完美這個東西在工程當中是否真的存在,因爲考慮到成本,時間以及技術等等各方面的限制,要做到讓各方面都滿意實際並不現實。總結過往的經驗,基於已有的信息做大膽假設,結合實際需求做出一個取捨,是非常必要的。

爲了處理分組丟失的情況,TCP會採取擁塞避免算法。
在一個TCP的連接上,發送數據的速率同時受通告窗口和擁塞窗口約束,這是TCP的流量控制。在擁塞窗口大小超過通告窗口之前,TCP會通過慢啓動的方式不斷擴大擁塞窗口,直到擁塞窗口超過通告窗口大小或者信道發生擁塞,TCP分組丟失。

擁塞避免算法本質是爲了解決傳輸速率過快導致的分組丟失的問題。雖然擁塞避免算法和慢啓動算法是兩個目的不同,獨立的算法,但是兩者通常會在一起實現。對於一個TCP連接,擁塞避免算法和慢啓動算法維持了兩個變量:擁塞窗口和慢啓動門限ssthresh。

先簡單介紹一下,sshthresh是一個閾值。當cwnd小於等於ssthresh時TCP採取的是慢啓動算法,當突破這個閾值之後TCP會轉爲擁塞避免算法。詳細的分析我們在下面的算法過程中繼續分析。

具體的算法過程如下:

  1. 對於一個給定的連接,初始化cwnd爲1個報文段,ssthresh爲65535個字節。

這裏需要強調的是兩個變量的單位是不一樣的!cwnd是以報文段爲單位,大小是三次握手約定的MSS;而ssthresh這個閾值在開始則被置爲TCP單個報文的最大長度。

65535這個大小限制是因爲報文頭部描述報文長度的字段字節數限制的。當然我們可以通過選項擴大這個限制。

2)TCP的輸出不能超過cwnd和通告窗口

這個非常簡單,是我們強調很多遍的流量控制。擁塞窗口是發送方對於網絡擁塞的試探和估測,而通告窗口則是接收方對於自身緩衝區可用大小的描述。

3)當擁塞發生時,sshthresh會被置爲當前窗口大小的一半。如果是超時引起的擁塞,那麼cwnd會被設置爲1個報文段

首先我們要考慮的是擁塞發生的情況:第一種是我們上文提到的分組丟失導致的超時;另一種是連續的數據報丟失了其中的一個,後續報文沒有丟失發送端會收到重複確認。

我們假設分組丟失是由於網絡擁塞造成的,對於上述的兩種情況TCP的判斷是不一樣的:超時被認爲網絡擁塞嚴重幾近癱瘓;而重複確認則是網絡發生了一點擁塞,影響了部分通信但網絡目前仍然可用。基於這樣兩個判斷TCP採取了不同的處理方案,重複確認的情況會觸發TCP的快速重傳和快速恢復算法,這一部分我們放在後面詳細介紹,這裏先考慮超時這一種情況。

讓我們重新回頭來看sshthresh。超時導致的擁塞發生時,sshthresh會被置爲當前窗口大小的一半,要了解這樣設置的原因,我們需要重新認識一下什麼是sshthresh。

TCP的傳輸速率同時受通告窗口和擁塞窗口約束,這裏我們假設通告窗口的大小保持不變,而擁塞窗口根據慢啓動算法以指數的形式增長。

窗口變化
擁塞通常應該是發生在t1之前,也就是擁塞窗口小於等於通告窗口之前。假設擁塞窗口擴大到通告窗口之後仍未發生擁塞,那麼之後最小窗口會一直受通告窗口約束,一般情況下不可能再發生擁塞。基於這樣一個判斷,在擁塞發生時TCP會更多關注擁塞窗口的大小。

讓我們重新回顧慢啓動算法下擁塞窗口的擴大規則:1,2,4,8… 以指數級增長。考慮這樣一個情況

收到確認 窗口大小 是否發生擁塞
1 1 False
2 2 False
3 4 False
4 8 False
5 16 False
6 32 True
假設在時刻6擁塞窗口擴大到32個報文段大小的時候發生了擁塞,我們可以得到這樣一個信息:合適的窗口大小範圍應該在16 ~ 32個報文段之間。sshthresh設置爲當前發生擁塞的窗口大小的一半,實際是爲了記錄上一個可用的擁塞窗口大小。

因爲擁塞窗口的急劇縮小,發送端的傳輸速率會發生驟降。

4)當新的數據被對方確認時會增加cwnd,但具體增加的方式取決於當前是在進行慢啓動還是擁塞避免算法

當cwnd小於sshthresh時,TCP採取的是慢啓動算法;當cwnd慢慢擴大到sshthresh時TCP會轉爲擁塞避免的算法來增加cwnd。

這個策略非常容易理解。考慮之前的例子,sshthresh記錄的是上一個可用的擁塞窗口大小,那麼我們認爲當cwnd小於這個大小時可以放心的採用慢啓動算法;但是當cwnd達到這個閾值以後,很顯然慢啓動算法已經不再合適,擁塞窗口的指數級擴大會造成網絡擁塞,所以我們改換了策略來避免擁塞的發生,這就是擁塞避免算法。

擁塞避免算法要求每次收到一個確認會將cwnd增加1/cwnd,和慢啓動的指數增加相比,擁塞避免算法是一種加性的增加。TCP希望在一個RTT內cwnd最多增加一個報文段大小。

慢啓動和擁塞避免算法可視化描述
書中21.6介紹擁塞避免算法確實使用的是每次增加1/cwnd,但是很顯然這個公式並不合理。因爲後續無限疊加下去結果一定小於2/cwnd(參考1 + 1/2 + 1/3 + … )

在21.8中實際舉了一個擁塞避免的例子,這一節給出了實際的計算過程。其中擁塞避免發生時cwnd的計算公式如下

cwnd = (segsize * segsize)/cwnd + segsize/8

我不太理解1/cwnd的增加方式是如何理解的,個人更傾向於認爲這是一種錯誤的打印。

需要強調的是爲了描述和理解的方便,我們更傾向於使用報文段來作爲單位描述兩個變量,但實際上兩者都是以字節爲單位來進行維護的。

總結
擁塞避免算法本質上是爲了應對超時這樣一種擁塞情況做出的應對措施,替換慢啓動,以一種更加平緩線性的增長方式來擴大擁塞窗口,從而不斷試探逼近網絡的極限。

在擁塞發生的時候受cwnd會被重置爲1,這會導致TCP的傳輸速率發生跳水。理由也非常的簡單:我們認爲此時網絡幾近癱瘓,發送端降低傳輸速率讓網絡有時間恢復通暢是十分有必要的。

TCP的快速重傳和快速恢復
現在讓我們審視擁塞的另一種情況:連續傳輸的報文中間丟失了一個分組。後續報文的送達會讓接收端立即產生一個重複的ack返回給發送端,並且這個ack不應該被遲延。舉一個簡單的例子:

圖上的報文段3丟失,但是後續的報文段4 5 6成功送達。接收端會不停的告訴發送端ack 2。之前文章我說過,可以把ack 2理解爲報文段2之前的數據都已經被送達確認啦,但是在這裏送達確認並不適合,我們改換一種理解的方式,將ack 2理解爲報文段2之前的數據已經被送達確認啦,請給我報文段3。

讓我們重新查看上圖:報文4 5 6的送達會讓接收端不停的向發送端發出請求:“請給我報文段 3”。從這個對話中可以挖掘出什麼信息?

接收端沒有收到報文段3

但是接收端收到了報文段3之後的三個報文段

通常情況下,發送端一個報文段如果只發送順利,那麼應當至多收到一個來自接收端的確認。但是上篇文章介紹過成塊數據流的傳輸,TCP會不等待接收端的確認就將數據報發送出去,雖然我們發送是有序的,但傳輸的過程是無序的,我們無法保證數據報有序送達接收端。

對於發送端而言實質上無法判斷重複ack究竟是因爲報文段丟失還是幾個報文段傳遞過程發生了亂序。考慮到這樣一個情況,在收到1 - 2 次重複ack時TCP認爲可能是中間發生了亂序,但是如果重複ack的次數到達3之後,TCP有理由認爲這個分組已經丟失,那麼此時TCP會立即重傳丟失的報文段,無需等待超時定時器的溢出,這就是快速重傳算法。

具體的過程如下:

1)當收到3個重複的ack時,將ssthresh設置爲當前cwnd的一半。重傳丟失的報文段,並設置cwnd爲sshthresh加上3倍報文的大小

sshthresh的設置和超時情況一致,區別的地方在於超時引起的擁塞cwnd會被置爲1個報文段大小;而報文段丟失則是將cwnd設置爲sshthresh加上3倍報文段大小。我們必須思考兩種策略不同的原因。

回想上文所說,TCP認爲超時引起擁塞時,當前網絡幾近癱瘓;而報文段丟失的情況,網絡雖然發生擁塞但仍然保持工作。假設第二種情況下如果我們仍然將cwnd設置爲1個報文段大小會發生什麼?

擁塞情況
因爲cwnd的驟減(突變爲1),發送端將在長時間內不能發送任何一個報文!對於發送端而言,實際可用的窗口大小等於Min(通告窗口,擁塞窗口)減去處於inflight狀態下報文段的大小。

實際窗口
很顯然發生快速重傳時發送端至少有4個未被確認的報文段(丟失的報文段 + 3個引起重複確認的報文段)。當這個疑似丟失的報文段重傳成功之後,接收方會同時返回多個ack的確認,原因很簡單,丟失報文段後面的數據早已經被送達接收端。

如果我們採取和超時一樣的策略,按照慢啓動算法,cwnd會迅速的擴大。對於TCP而言,發送端的傳輸速率會幾乎停止,等待一個RTT左右的時間,然後速率又突然暴漲。

很顯然傳輸速率這樣大的一個波動是很難接受的。

爲此cwnd並沒有被置爲1,而是設置成了sshthresh + 3倍報文的大小。TCP認爲當前網絡並沒有癱瘓還可以繼續工作,所以慢啓動的過程被省略。加上3倍報文大小是因爲連續收到了三次重複ack,TCP會樂觀的認爲丟失報文段之後至少已經發成功三個報文段,那麼我允許你的窗口透支這部分的大小。

2)快速重傳之後,如果繼續收到重複確認,那麼每一個重複確認都會讓cwnd擴大一個報文段的大小

實際這裏cwnd擴大的原因和第一步中+ 3個報文段大小的原因類似,本質都是發送端在透支擁塞窗口的大小。TCP這麼做的根據如下:

網絡仍然在正常工作

cwnd = cnwd/2 調整之後已經縮小到一個安全的閾值

透支額度是爲了應對處於inflight狀態的報文段壓縮實際可用的擁塞窗口大小

3)當下一個新的報文段確認到達之後,cwnd會被調整爲sshthresh,也就是第一步中發生報文段丟失時cwnd的一半。

通常情況來說,新的報文段確認除了確認重傳數據的送達,也應該會對丟失的分組和收到第一個重複ack之間發出的所有報文段進行確認。

cwnd調整的原因就在於此,我們在第1) 2)步驟中透支的窗口大小是考慮到處於inflight狀態的報文段,但新的確定到來時這些處於inflight的報文段都得到了送達確認,那麼透支的窗口大小就應該被還回。

這部分的策略我們稱之爲快速恢復。

我們舉一個簡單的例子來說明快速恢復策略的優勢。假設TCP在傳輸101報文時發生了丟失,觸發快速重傳,假設cwnd = 10,那麼發送端發送完110報文會停止發送。

如果不使用快速恢復算法
發送端收到的報文 收到報文的解釋 cwnd當前的大小 註釋 當前發送端的狀態
ack 101 接收端收到102 10 第一次重複確認 停止發送
ack 101 接收端收到103 10 第一次重複確認 停止發送
ack 101 接收端收到104 10 第一次重複確認 停止發送
觸發快速重傳
ack 101 接收端收到105 5 inflight packet = 10 > cwnd 停止發送
ack 101 接收端收到106 5 inflight packet = 10 > cwnd 停止發送
ack 101 接收端收到107 5 inflight packet = 10 > cwnd 停止發送
ack 101 接收端收到108 5 inflight packet = 10 > cwnd 停止發送
ack 101 接收端收到109 5 inflight packet = 10 > cwnd 停止發送
ack 101 接收端收到110 5 inflight packet = 10 > cwnd 停止發送
時間應該小於一個RTT 等待101確認
ack 110 5 此時開始新的數據發送
使用快速恢復算法
發送端收到的報文 收到報文的解釋 cwnd當前的大小 註釋 當前發送端的狀態
ack 101 接收端收到102 10 第一次重複確認 停止發送
ack 101 接收端收到103 10 第一次重複確認 停止發送
ack 101 接收端收到104 10 第一次重複確認 停止發送
5 + 3 = 8 觸發快速重傳
ack 101 接收端收到105 9 inflight packet = 10 > cwnd 停止發送
ack 101 接收端收到106 10 inflight packet = 10 > cwnd 停止發送
ack 101 接收端收到107 11 inflight packet = 10 = cwnd 發送 111
ack 101 接收端收到108 12 inflight狀態的報文數量大於cwnd 發送 112
ack 101 接收端收到109 13 inflight狀態的報文數量大於cwnd 發送 113
ack 101 接收端收到110 14 inflight狀態的報文數量大於cwnd 發送 114
ack 110 5 觸發快速恢復
擁塞避免算法
快速恢復的優勢顯而易見。

總結
快速重傳和快速恢復本質是TCP對擁塞不嚴重的情況做出的特殊處理,避免發送端傳輸速率大起大落,能夠保持一個較爲穩定的速率一直傳輸。

至此,有關TCP的基礎擁塞處理策略大概介紹完畢。花了這麼大篇幅介紹擁塞避免的處理,實際意義並不是讓讀者要去死摳其中某個算法的實現細節,而是理解設計的思想,爲什麼會去這樣設計。因爲同一套策略可能面對不同的網絡,表現是完全不一樣的。

我認爲,很多時候工程上的問題單獨來看實際是無解的。所謂解決問題的策略,都是建立在一個實驗數據的基礎之上,從數據中總結出相關現象的規律,並以此爲依據來進行策略的指定。當工程的環境發生改變,依賴的前提發生了改變,那麼之前的策略反而可能成爲累贅。

TCP就是一個很好的例子。歷史的慣性很多時候不允許我們隨意的推翻已有的技術,所以更多的時候我們會選擇對原有技術進行修修補補。後面的文章我們會開一個小節,來簡單談談TCP的現在和未來,瞭解一下它是如何拓展來適應現代的網絡環境的。

TCP的定時器
對於每一個連接,TCP管理着四個不同的定時器,之前的文章裏我們已經介紹過三個了,分別是:

重傳定時器。用於判斷接收端消息確認是否超時,時間的設定和RTT相關,TCP根據超時來設定擁塞避免的策略。

保活定時器。TCP連接會定時的檢測一個空閒的連接是否仍然正常保持,可惜這個保活策略並不可靠。通常情況下應用層協議並不依賴TCP的保活機制,而是根據自身的應用場景指定額外的保活機制。

2MSL定時器。用以測量一個TCP連接處於TIME_WAIT的時間。

最後一個定時器和TCP的窗口相關:

堅持定時器。在收到零窗口的消息之後,TCP發送端會週期性地向接收端查詢窗口是否擴大。
通告窗口是接收端採取的流量控制。當接收端無法繼續接收新的數據時候,可以通過零窗口來阻止發送端繼續傳送數據,直到接收端處理結束,進行窗口更新,發送端繼續傳送數據。

但是窗口更新本質是一條重複的ack數據,只不過這條ack消息裏通告窗口的大小發生了變化而已。回想文章開始我們說過的一句話,文件傳輸的過程是單向確認,也就是發送端確認送達即可。問題在於窗口更新的ack對應消息之前已經被髮送端確認了,如果這條窗口更新這條報文丟失,那麼雙方將陷入死鎖。爲此發送端設置堅持定時器來主動查詢窗口更新。

我們可以簡單模擬這樣一種情況:發送端嘗試不斷髮送數據,接收端在accept()之後執行sleep(),讓發送的數據堆積在緩衝區即可。

堅持定時器
保活定時器
對於一些需要維持長連接的業務場景,保活機制是必不可少的。但是TCP本身提供的保活選項大多數情況下並不適用,具體的討論我們放在了系列文章的第一篇,所以保活的功能一般會由各自的應用層協議來進行實現。

爲此,我們不再討論TCP的保活定時器。

TCP的現在
TCP設計之初是以撥號SLIP鏈路和以太數據鏈路爲參考的,在80和90年代以太網是允許TCP/IP最主要的數據鏈路方式。但隨着時代的推進,以太網的速率不斷提高,無限網絡的普及,TCP原先的設計可能不再適應。

路徑MTU的發現
在三次握手的過程當中,TCP會約定MSS作爲最大報文段來避免分片,但是顯然存在一些缺陷

MSS實際只受通信兩端MTU的影響,中間路由的MTU無法判斷

MSS是一個靜態的數值,無法動態感知數值的變化

爲了解決發現中間網絡的MTU這個問題,TCP設計了路徑MTU發現機制。這個機制必須要和IP協議配合實現,利用的就是IP協議中DF(Don’t Framement)字段的設置。當DF爲1時如果報文大小超過中間路由的MTU,那麼中間路由會拒絕分片並返回ICMP 不可達的差錯報文。

ICMP的差錯報文會通知TCP減少報文段大小進行重傳。如果仍然收到同一個中間路由的ICMP差錯報文,那麼TCP就需要繼續嘗試下一個可能的最小MTU。

中間路由是動態變化的,那麼也就是說MTU可能會發生改變,這也是我們爲什麼要選擇路徑發現MTU的原因。考慮MTU縮小的情況,那麼我們也必須考慮到MTU增大的情況,畢竟維持一個較小的MTU傳輸並不是一個高效的選擇。路徑MTU發現機制會在減少MTU一段時間之後,嘗試使用一個較大的MTU來進行測試。

當然擴大的上限仍然是MSS

IPv6協議期待支持的設備提供的MTU最小是1280,目的就是爲了避免MTU發現的過程。應該來說MTU相關的機制都是爲了解決硬件設備標準不統一帶來的麻煩。

長肥管道
上一篇文章的結尾我們討論了帶寬時延乘積,用以描述通信管道的容量。當這個容量不斷擴大時,TCP就顯得有些力不從心了,我們描述這種具有較大帶寬時延乘積的管道,稱之爲長肥管道。

這個描述實際非常貼切。如果想要提高帶寬時延乘積,可以水平拉長也就是RTT擴大,可以是垂直拉高也就是帶寬增大,或者是兩者同時拉伸。在這種情況下TCP會遇到很多問題

TCP首部中的描述報文大小的字段是16bit,這也就說窗口最大的限制是65535。在長肥管道上,現有的窗口大小無法把管道充滿,換句話說也就是資源利用的不充分。
爲了解決這個問題,TCP新增了窗口擴大選項:允許TCP把最大窗口大小拓展到32 bit來描述。當然,這並沒有修改TCP首部的內容,因爲我們必須考慮到和老版本的兼容性。TCP的做法是在選項當中使用一個字節來描述擴大因子x,範圍是0 - 14。最大窗口會在這個基礎上擴大,2 ^ 16 * 2 ^ x。

這個參數的約定是在三次握手的過程,前提是雙方都必須支持這個選項。如果接收端不支持,那麼發送端會將這個字節設爲0用以兼容舊的實現。

需要注意的是發送雙方的窗口擴大因子可以不一致。

長肥管道上的多分組丟失,現有的算法仍然效率不高。

現有的RTT計算方式對於長肥管道並不合適。

考慮這樣一個簡單的情況:RTT是對一個數據信號(包含數據的報文段)以較低的頻率(每個窗口一次)進行採樣的,假設窗口的大小爲x個報文段大小,那麼採樣速率也就是1/x。當窗口較小時採樣速率是可以接收的,但在長肥管道上x變大,這個時候採樣速率就會變得很小,RTT相對會不太精準。

對於依賴RTT來進行判斷的重傳策略,一個不精確的RTT是不可接受的。錯誤的擁塞判斷會導致傳輸速率大大降低

長肥管道上很容易出現Sequence Number環回。
Sequence Number的環回問題在原先的網絡上幾乎不可能出現。但在長肥管道上由於帶寬時延乘積較大,並且可能使用了窗口擴大選項,使得Sequence Number的環回速度加快,一個MSL內很容易發生環回。

爲了解決這個問題,TCP引入了時間戳選項。發送端會在每一個發出的報文當中放入一個時間戳,而接收方在ack裏會將這個時間戳返回。需要注意的是雖然時間戳是在兩端時間傳遞,但實際只有發送端會據此進行比對和判斷,接收端只是簡單的回顯,所以這個時間戳的值不需要兩端同步校驗。

對於處理Sequence Number環回的問題,時間戳實際就是一個單向遞增的數字,作爲Sequence Number的拓展,輔助判斷每個報文的排列順序。這就是PAWS算法。

時間戳選項的引入實際還可以幫助我們進行更準確的RTT計算,發送端可以根據每一個ack來進行RTT的計算,而不是窗口大小。

依賴每一個ack而不是每個報文的原因是,雖然發送的報文都攜帶有時間戳,但接收端的可能會合並多個報文段的確認。

TCP會維持兩個狀態變量用於時間戳的處理:tsrecent和lastack,分別記錄下一個ack將要發送的時間戳和最後發送的ack中的確認序號。

這基本是書中翻譯的原話,非常的含糊!因爲書中並沒有說明具體是誰來維持這兩個變量,對於TCP而言是有發送端和接收端兩個對象的,計算RTT是發送端的需求,所以很容易錯誤的理解成這兩個變量由發送端維持。實際這部分是由接收端負責,解決的問題是在確認多個報文的ack中如何設置時間戳的問題。

試想,接收端收到了報文段1(1 - 1024)和報文段2(1025 - 2048)之後,用一個ack進行確認,laskack非常容易理解,確認序號應該是2048,但時間戳應該選擇哪一個報文?

選擇報文段1和報文段2時間戳的不同在於,是否包含了ack捎帶這一部分的時間。很顯然,對於RTT的計算ack捎帶這部分的延遲時間是應該考慮的,所以返回的ack應當選擇報文段1的時間戳。這個時間戳由變量tsrecent保存。

當發送端收到ack之後,可以取出時間戳和本地時間比對,計算RTT時間。因爲時間戳都是以發送端爲標準的,所以無需額外的處理。

和窗口擴大因子選項類似,這些新增的選項都需要在握手過程中互相確認,是否支持。這是爲了兼容舊版本所必須的操作。

BBR算法
這部分並未在《TCP/IP 卷一》中提及,但在提及TCP的現在和未來時是一個不可繞開的話題。作爲新的擁塞避免控制算法,BBRs算法擯棄了經典擁塞避免算法對於擁塞的理解和判斷。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章