TCP 接收窗口自動調節


https://technet.microsoft.com/zh-cn/magazine/2007.01.cableguy.aspx

歡迎來到 TechNet 雜志“網絡專家”的第一部分。TechNet 網站上的專欄愛好者都知道我們探討各種網絡問題,我們每個月都將繼續保持這個傳統。如果您是新手,要查找以前專欄的存檔,請訪問 網絡專家站點
現在開始介紹我們的第一個主題 - TCP 接收窗口。
TCP 連接的吞吐量可以通過發送和接收應用程序、發送和接收 TCP 的實現以及 TCP 對等方之間的傳輸路徑來限制。在本專欄中,我將介紹 TCP 接收窗口及其對 TCP 吞吐量的影響、TCP 窗口縮放的使用以及 Windows Vista™ 中的“接收窗口自動調節”新功能(可優化 TCP 接收數據吞吐量)。

 

TCP 接收窗口
TCP 連接具有許多重要的特點。首先,它們是兩個應用層協議之間的邏輯點對點通信。TCP 不提供一對多的傳遞服務,它僅提供一對一的傳遞服務。
其次,TCP 連接是面向連接的。在數據可以傳輸之前,兩個應用層過程必須通過 TCP 連接建立過程正式協商一個 TCP 連接。同樣,TCP 連接在通過 TCP 連接終止過程協商之后正式關閉。
再次,在 TCP 連接中發送的可靠數據按順序排列,且期望得到接收端的肯定確認。如果沒有接收到肯定確認,則重傳這個段。接收端一端會放棄重復的段,並按照正確順序排列到達時失序的段。
最后,TCP 連接是全雙工的。對於每個 TCP 對等方,TCP 連接都由兩個邏輯管道組成:一個傳出管道和一個傳入管道。TCP 報頭包含傳出數據的序列號和傳入數據的確認 (ACK)。
此外,TCP 將通過傳入和傳出邏輯管道發送的數據視為連續的字節流。每個 TCP 報頭中的序列號和確認號都根據字節邊界定義。TCP 並不會考慮字節流中的記錄或消息邊界。應用層協議必須正確地分析傳入的字節流。
為了限制任一時刻可發送的數據量,並為接收端提供流量控制,TCP 對等方使用窗口實現這些目的。該窗口是接收端允許發送端發送的字節流的數據范圍。發送端只能發送位於窗口內的字節流中的字節。該窗口隨着發送端的出站字節流和接收端的入站字節流而滑動。
對於給定的邏輯管道(全雙工 TCP 連接的一個方向),發送端維護一個發送窗口,接收端維護一個接收窗口。當傳輸中沒有數據或 ACK 段時,邏輯管道的發送和接收窗口相互匹配。換句話說,發送端允許發送的出站字節流中的數據范圍與接收端能夠接收的入站字節流中的數據范圍相匹配。 圖 1說明了這種發送和接收關系。
圖 1  匹配發送和接收窗口 (單擊該圖像獲得較大視圖)
為了表示接收窗口的大小,TCP 報頭包含了一個 16 位的“窗口”字段。當接收端收到數據時,它把 ACK 發送回發送端以表明成功接收到這些字節。在每個 ACK 中,“窗口”字段表示接收窗口中剩余的字節數。當應用程序發送、確認和檢索數據后,發送窗口和接收窗口都會滑動到右側。接收窗口是用於控制可從發送端傳送給接收端的未確認數據數量的窗口。
由於接收窗口中可能會有應用程序未檢索到的數據以及已接收但尚未確認的數據,因此 TCP 接收窗口具有一些其他的結構,如 圖 2 所示。
圖 2  TCP 接收窗口中的數據類型 (單擊該圖像獲得較大視圖)
請注意最大接收窗口和當前接收窗口的區別。最大接收窗口的大小是固定的。當前接收窗口的大小是可變的,並對應於接收端允許發送端發送的剩余數據量。當前接收窗口大小是發送回發送端的 ACK 中通告的“窗口”字段值,等於最大接收窗口大小與已接收和確認但尚未被應用程序檢索的數據量之間的差值。

 

TCP 接收窗口和 TCP 吞吐量
為了優化 TCP 吞吐量(假設為合理的無差錯傳輸路徑),發送端應該發送足夠的數據包以填滿發送端和接收端之間的邏輯管道。邏輯管道的容量可由以下公式計算:
 
Capacity in bits = path bandwidth in bits per second * round-trip time (RTT) in seconds
容量稱為帶寬延遲乘積 (BDP)。管道可以用粗(高帶寬)和細(低帶寬)或者長(高 RTT)和短(低 RTT)來表示。粗而長的管道的 BDP 最高。使用高 BDP 傳輸路徑的示例包括衛星鏈接或帶有洲際光纜鏈接的企業廣域網 (WAN)。
增強高 BDP 傳輸的發送端性能
新的“接收窗口自動調節”功能增強了通過高 BDP 鏈接接收數據的性能,但是發送端的性能如何呢?
避免發送 TCP 對等方擁塞整個網絡的現有算法被稱為“慢啟動”和“擁塞避免”。在連接最初發送數據和還原丟失段時,這些算法可以增大發送窗口,即發送端可以發送的段數量。
對於每個接收到的確認段(Windows XP 和 Windows Server 2003 中的 TCP)或每個已經確認的段(Windows Vista 和 Windows Server“Longhorn”中的 TCP),“慢啟動”算法會以一個完整的 TCP 段增大發送窗口。對於每個已經確認的完整窗口的數據,“擁塞避免”算法以一個完整的 TCP 段增大發送窗口。
這些算法很好地適應較小的 BDP 和較小的接收窗口大小。然而,當面對一個具有較大接收窗口大小和較大 BDP 的連接時,例如在位於高速 WAN 鏈接(往返時間為 100 毫秒)上的兩個服務器之間復制數據,利用這些算法增大發送窗口的速度就不足以充分利用連接帶寬。
為了在上述情形下更好地利用 TCP 連接的帶寬,下一代 TCP/IP 堆棧包括了復合 TCP (CTCP)。CTCP 可以更快地增大發送窗口,適用於擁有較大的接收窗口大小和 BDP 的連接。CTCP 試圖通過監控延遲變化和損失來使這類連接的吞吐量最大化。此外,CTCP 確保其行為不會對其他 TCP 連接產生負面影響。
在 Microsoft 內部執行的測試中,對於 RTT 為 50 毫秒、傳輸速率為每秒 1 Gb 的連接,大型文件的備份時間幾乎縮短了一半。對於具有更大 BDP 的連接,性能的改善更為明顯。CTCP 和“接收窗口自動調節”配合使用,可以提高鏈接利用率,最終可以顯著提高具有較大 BDP 的連接的性能。
默認情況下,運行 Windows Server“Longhorn”的計算機上啟用 CTCP,而運行 Windows Vista 的計算機上則禁用 CTCP。可以使用“netsh interface tcp set global congestionprovider=ctcp”命令啟用 CTCP,還可以使用“netsh interface tcp set global congestionprovider=none”命令禁用 CTCP。
TCP 報頭中的“窗口”字段大小為 16 位,允許 TCP 對等方通告最大為 65,535 字節的接收窗口。您可以根據以下公式計算給定 TCP 窗口的近似吞吐量:
 
Throughput = TCP maximum receive windowsize / RTT
例如,對於 65,535 字節的接收窗口,在 RTT 為 100 毫秒的路徑上只能達到速度大約為每秒 5.24 兆字節 (Mbps) 的吞吐量,而不管傳輸路徑的實際帶寬是多少。對於目前的高 BDP 傳輸路徑,最初設計的 TCP 窗口大小即使達到最大值,仍然是吞吐量的瓶頸。

 

TCP 窗口縮放
為了提供可適應高速傳輸路徑的更大窗口尺寸,RFC 1323 ( ietf.org/rfc/rfc1323.txt) 定義了允許接收端通告大於 65,535 字節的窗口大小的窗口縮放。“TCP 窗口縮放”選項包括一個窗口縮放因子,該因子與 TCP 報頭中的 16 位窗口字段結合時,可以將接收窗口大小最大增加到 1GB。“TCP 窗口縮放”選項只用於在連接建立過程的同步 (SYN) 段中發送數據。TCP 對等方都可以為接收窗口大小指定不同的窗口縮放因子。TCP 窗口縮放允許發送端通過一個連接發送更多的數據,可以使 TCP 節點更好地利用一些具有高 BDP 的傳輸路徑類型。
盡管接收窗口大小對於 TCP 吞吐量而言非常重要,但確定最佳 TCP 吞吐量還有一個重要的因素,那就是應用程序檢索接收窗口中累積數據的速度(應用程序檢索速率)。如果應用程序不能檢索數據,接收窗口就可以開始填充,導致接收端通告了更小的當前窗口大小。在極個別的情況下,整個最大接收窗口都會填滿,導致接收端通告了 0 字節的窗口大小。在這種情況下,發送端必須停止發送數據,直到接收窗口已經清除為止。因此,要優化 TCP 吞吐量,必須將連接的 TCP 接收窗口設定為可同時反映該連接傳輸路徑的 BDP 和應用程序檢索速率的值。
即使您可以正確地確定 BDP 和應用程序檢索速率,它們仍會隨時間而變化。BDP 速率可以根據傳輸路徑中的阻塞情況而變化,而應用程序檢索速率會根據應用程序檢索數據所在的連接數量而變化。

 

Windows XP 中的接收窗口
對於 Windows XP(和 Windows Server ® 2003)中的 TCP/IP 堆棧,最大接收窗口的大小具有許多重要的屬性。首先,默認值基於發送界面的鏈接速度。實際值自動調整為 TCP 連接建立過程中協商的最大段大小 (MSS) 的偶數增量。
其次,最大接收窗口的大小可以手動配置。可將注冊表項 HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\TCPWindowSize 和 HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\Interface\InterfaceGUID\TCPWindowSize 的值設置為最大 65,535 字節(不帶窗口縮放)或 1,073,741,823 字節(帶窗口縮放)。
再次,最大接收窗口的大小可以使用窗口縮放。可通過將注冊表項 HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\Tcp1323Opts 的值設置為 1 或 3 來啟用窗口縮放。默認情況下,僅當接收的同步 (SYN) 段包含“窗口縮放”選項時,才在連接上使用窗口縮放。
最后,啟動連接時,可使用應用程序的“SO_RCVBUF Windows Sockets”選項,指定連接的最大接收窗口大小。使用窗口縮放時,應用程序所指定的窗口大小必須大於 65,535 字節。
盡管 Windows XP 支持可縮放窗口,但其中的最大接收窗口大小仍然會限制吞吐量,因為它是針對所有 TCP 連接(除非由應用程序指定)的一個固定的最大大小,它可以增加某些連接的吞吐量,同時減少其他連接的吞吐量。另外,TCP 連接的最大接收窗口大小固定,不隨應用程序檢索速率的變化或傳輸路徑中的阻塞而變化。

 

Windows Vista 中的接收窗口自動調節
為了優化 TCP 吞吐量,特別是具有高 BDP 的傳輸路徑,Windows Vista(和代碼名為“Longhorn”的 Windows Server 下一版本)中的下一代 TCP/IP 堆棧支持接收窗口自動調節功能。該功能通過測量 BDP 和應用程序檢索速率,以及調整當前的傳輸路徑和應用程序狀況的窗口大小,來確定最合適的接收窗口大小。
默認情況下,“接收窗口自動調節”會啟用 TCP 窗口縮放,它所允許的最大窗口大小為 16 MB。數據流通過連接時,下一代 TCP/IP 堆棧會監控連接,測量該連接當前的 BDP 和應用程序檢索速率,並調整接收窗口大小以優化吞吐量。下一代 TCP/IP 堆棧不再使用 TCPWindowSize 注冊表值。
“接收窗口自動調節”功能具有許多優點。它可以根據每個連接自動確定最佳的接收窗口大小。在 Windows XP 中,TCPWindowSize 注冊表值適用於所有的連接。應用程序無需再通過“Windows Socket”選項指定 TCP 窗口大小。並且 IT 管理員也無需再為特定的計算機手動配置 TCP 接收窗口的大小。
使用“接收窗口自動調節”功能后,基於 Windows Vista 的 TCP 對等方通常會比基於 Windows XP 的 TCP 對等方通告更大的接收窗口大小。這使得其他 TCP 對等方可以通過發送更多的 TCP 數據段來將管道填入基於 Windows Vista 的 TCP 對等方,而無需等待 ACK(服從 TCP 擁塞控制)。對於如網頁或電子郵件等典型的基於客戶端的網絡通信,Web 服務器或電子郵件服務器可以向客戶端計算機更快更多地發送 TCP 數據,從而全面提高網絡性能。連接的 BDP 和應用程序檢索速率越高,性能的提高就越明顯。
在數據傳輸中,將通常以較慢的速度發送的 TCP 數據包流快速發送,從而導致網絡利用率出現更大的高峰,這就是該方法對網絡造成的影響。基於 Windows XP 和 Windows Vista 的計算機在長而粗的管道上執行相同的數據傳輸時,傳輸的數據量相同。然而,基於 Windows Vista 的客戶端計算機的數據傳輸更快,因為其具有更大的接收窗口大小,並且服務器能夠填充從自身到客戶端的管道。
由於“接收窗口自動調節”會增加高 BDP 傳輸路徑的網絡利用率,因此對於達到或接近最大容量的傳輸路徑,限制使用服務質量 (QoS) 或應用程序發送速率可能會變得非常重要。為了滿足此潛在的需要,Windows Vista 支持基於組策略的 QoS 設置,可以利用該設置為基於 IP 地址或 TCP 端口的信息流發送速率定義限制速率。有關詳細信息,請參閱 基於策略的 QoS 資源
增加高損失網絡的 TCP 吞吐量
在高損失網絡中,頻繁的超時和重傳可能會大大降低 TCP 吞吐量。高損失網絡的一個例子就是無線網絡,如基於 IEEE 802.11、通用分組無線業務 (GPRS) 或通用移動通信系統 (UMTS) 的網絡,由於網絡狀況、信號衰減、電磁干擾和計算機的位置變化,可能會有較高的數據包丟失率。
下一代 TCP/IP 堆棧支持以下四種 RFC,可以優化高丟失率環境中的吞吐量:

RFC 2582:The NewReno Modification to TCP's Fast Recovery Algorithm(TCP 快速恢復算法 NewReno 修正)
RFC 2001 中定義的快速恢復算法基於 Reno 算法,由於快速重傳事件而重傳段時,Reno 算法可增加發送端能發送的數據量。Reno 算法適用於單個丟失的段,有多個丟失段時就不適用了。當數據窗口中的多個段丟失且發送端收到部分確認(僅對成功接收的那部分數據的確認)時,NewReno 算法通過更改快速恢復過程中發送端可以用來提高發送速率的方法,提供更大的吞吐量。

RFC 2883:An Extension to the Selective Acknowledgment (SACK) Option for TCP(TCP 選擇確認 (SACK) 選項擴展)
RFC 2018 中定義的 SACK,允許接收端通過使用 SACK TCP 選項指示最多四個接收數據的非鄰接塊。RFC 2883 定義用於確認重復的數據包的 SACK TCP 選項中的字段的額外使用。發送端可以通過此操作確定何時重傳了不必要的段並調整其行為,以防今后不必要的重傳。發送的重傳越少,整體吞吐量越合理。

RFC 3517:A Conservative Selective Acknowledgment-based Loss Recovery Algorithm for TCP(TCP 的基於保守選擇確認的丟失恢復算法)
Windows Server 2003 和 Windows XP 中的 TCP/IP 當前實現只使用 SACK 信息確定未到達目標的 TCP 段。RFC 3517 定義了收到重復確認后使用 SACK 信息執行丟失恢復的方法,以便在連接上啟用 SACK 時替代原來的快速恢復算法。下一代 TCP/IP 堆棧基於每個連接跟蹤 SACK 信息並監控傳入確認和重復確認,以便在目標未收到多個段時更快進行恢復。

RFC 4138:Forward RTO-Recovery (F-RTO):An Algorithm for Detecting Spurious Retransmission Timeouts with TCP and the Stream Control Transmission Protocol (SCTP)(使用 TCP 和流控制傳輸協議 (SCTP) 探測偽重傳超時設定的算法)
RTT 突然增加時,可能會出現 TCP 段的偽重傳現象,導致先前發送的段的重傳超時設定 (RTO) 逐漸到期,TCP 開始重傳它們。如果 RTT 增加正好發生在發送整個窗口的數據前,發送端可能會重傳整個窗口的數據。F-RTO 算法通過以下行為防止 TCP 段的偽重傳。
當多個段的 RTO 到期時,TCP 只重傳第一個段。收到第一個確認后,TCP 開始發送新段(如果通告的窗口大小允許)。如果下一個確認確認超時但未重傳的其他段,則 TCP 確定超時是偽超時,不重傳超時的其他段。
這樣的結果是,對於 RTT 突然和臨時增加的環境(例如,當無線客戶端從一個入口點漫游到另一個時),F-RTO 可防止不必要的段重傳並更快地恢復到正常發送速率。基於 SACK 的丟失恢復和 F-RTO 最適用於使用 GPRS 鏈接的連接
 
http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28387257&id=3595033

一、RFC關於增大TCP初始窗口的討論

     http://tools.ietf.org/html/rfc2414
    詳盡的討論參見原文,這里做一部分摘錄,加上自己的理解
    增大起始窗口,和MTU探測等方面有關系【注1】。

1.1 增大初始窗口的好處

    1.1.1 可以及時回復ack

        這里牽涉到TCP的delay ack機制,普通情況下是每兩個段一個ack。
        如果起始窗口只有一個段大小,就需要等待delay ack機制的定時器超時,大約為40ms。【注2】
            關於delay ack詳細內容,參見另一篇博文《TCP delay ack機制和實現》(整理中)。    
        如果起始窗口大於等於兩個數據段,發送端就有足夠的數據段來觸發ack,防止了RTO。

    1.1.2 節省小文件傳輸時間

        如果連接傳輸少量數據,較大的初始窗口可以節省時間。
        例如郵件、網頁等較小的文件,傳輸時間甚至可以節省到一個RTT。
        在丟包率較高的情況下,也可以用大初始窗口來傳輸小文件節省時間。【注3】      

    1.1.3 節省大BDP網絡傳輸時間

        對於能使用大擁塞窗口的鏈路,這種改進可以在慢啟動階段節省多個RTT,並避免delay ack超時。
        這對於帶寬時延積(BDP)較大的鏈路,比如衛星鏈路,有特別的好處。

1.2 增大初始窗口的壞處

    1.2.1 對於用戶的壞處

        在高擁塞環境,特別對於路由器厭棄突發流的情況。【注4】
            TCP初始窗口為1,有時會比較好。
            例如,某些場景下,初始窗口為1的TCP包沒有被丟棄,窗口為4的遭遇了丟包。
            這是因為路由器能力有限,無法應對數據流的小突發。【注5】
            同時可能導致不必要的RTO,對於沒有對TCP流造成超時的情況,也會導致連接
            過早地從慢啟動轉移到擁塞避免階段。【注6】
        這種丟包和過早轉移在較低的擁塞環境,路由器buffer足夠的情況下不易發生。
            在平和擁塞環境下,如果路由器采用主動隊列管理【注7】,也不會發生。
        有時,即便有丟包和過早轉移,用戶依然能從較大的初始窗口中獲益
            1> 如果TCP連接沒有RTO,及時恢復
            2> TCP窗口被網絡擁塞或接收方通告窗口限制在一個較小范圍內。 【注8】      

     1.2.2 對於網絡的壞處

            就網絡擁塞崩潰方面的因素而言,可以考慮兩個潛在的危險場景。
            第一個場景是對端已經收到包,但鏈路極度擁塞,發送端不斷發送重復包,不斷超時。
            第二個場景是擁塞鏈路中的大量包在它們到達鏈路之前就會丟掉。
            增大擁塞窗口的潛在威脅是,加重了網絡總體的丟包率。三個問題討論:
             1> 重復段。較大的初始窗口可能引起丟包,但實驗表明,即便在這種情況下,也沒有大量重傳。
             2> 必然會被丟棄的段。在只有一段擁塞鏈路的網絡中,這些由較大初始窗口發送的多余段不會
                    浪費帶寬,也不會帶來擁塞崩潰。在有多條擁塞鏈路時,才可能浪費帶寬。但是,這種浪費
                    帶寬的行為對於初始窗口為1或4來說,都是相同的。【注9】
             3> 丟包率增長。較大的初始窗口在路由器執行隊尾丟包的情況下,會導致丟包率的進一步增加。
                   但並不會導致過大的丟包率增長。
    實驗結果參見原文。
            按個人理解來講,過大的初始窗口壞處在於:不公平,且在丟包恢復方面有負擔。
            在web瀏覽中,發起多個具有大窗口的連接是十分違反規則的。

二、Google建議將TCP初始窗口改為10

        sigcomm 2010
        《An Argument for Increasing TCP ’s Initial Congestion window》
        常見TCP初始窗口設置為3或4,大約4KB。
        在長流服務中,這種較小的初始窗口設定沒有什么問題。
        但是,如今常見的許多web服務數據流較短,比如一個頁面,可能只有4K-6K。
        在慢啟動階段,整個數據流的傳輸可能就會結束。
        此時,初始窗口的大小對於傳輸時間的長短起決定性作用。
        現有的chrome firefox等瀏覽器並發連接數為6,可以減少網頁的下載時間。
         http://www.iefans.net/bingfa-lianjieshu-sudu-ceshi/
        如果使用較大的初始窗口,速度改進將更加顯著。

2.1 提高初始窗口的好處

    2.1.1 減少發送延遲

        延遲計算公式如下,其中,S是傳輸塊大小,C是鏈路帶寬。
        
        不必在意公式細節,從直觀上理解,初始窗口越大,需要的總傳輸次數越少。
        比如初始窗口為10,當然要比初始窗口為1節省時間。

    2.1.2 跟上頁面的增長

        現有的很多web頁面大小有幾百KB,增大初始窗口能保持與時俱進。

    2.1.3 與長流競爭時,短流可以更加公平

        長流一般擁塞窗口已經很大,提高初始窗口可以讓短流更加公平。
        在TCP性能評估中,公平性是一個重要指標。

    2.1.4 提升丟包恢復速度

        這是理所當然的,初始窗口大了,丟包重傳時花的時間也就少了。

2.2 為什么是10

         根據Google的研究,90%的HTTP請求數據都在16KB以內,約為10個TCP段。

       對於較大數據量傳輸,初始窗口設定為10也能提升reponses的反應速度。
       將初始窗口設置為3、6、10、16、26、42,實驗結果表明,init_cwnd=16可以最大限度減少延遲。
       論文版權原因,不貼圖了,感興趣的請自己搜索下實驗圖表。
       如果窗口進一步提升,延遲會再增加,可能是因為丟包方面的負擔。
       詳細參見https://datatracker.ietf.org/doc/draft-ietf-tcpm-initcwnd/?include_text=1

          關於初始窗口為什么不取16,原文作者說道,他們發現在全球網絡環境實驗中,
            將初始窗口設置為16,在某些地區可能引起較差的反應。例如在東南亞,
            初始窗口取為16引起了明顯的丟包,這可能是過於擁塞的網絡環境和瀏覽器發起同步連接引起的。
            而初始窗口為10和16,性能差距不大,因此取10作為一個安全的值。  【注 10】

 

三、增大初始窗口代碼注意

        因為某些原因,不便詳述代碼方面的細節。
        可以提示一下:初始擁塞窗口過大時,會收到初始接收窗口的限制。

轉載請注明出處。
http://blog.chinaunix.net/uid-28387257-id-3595033.html
注:
【1】沒看太仔細,有興趣的同學可以看一下,歡迎討論
【2】原文寫的是RTO等待,這里有一個問題。
        接收端發送ack的過程是與delay ack超時相關的。
        RTO超時是指發送端等不到ack,重復發送數據段的過程。
【3】原文是:a larger initial window reduces the transmission time
         (assuming at most moderate segment drop rates).
        在穩健的高丟包情況下。存疑。
        這一點難以理解:丟包率固定的情況下,
        丟包數和總包數比例是一定的,並不會因為初始窗口的原因減少。
【4】對於高擁塞網絡,如果有突發流,路由器很可能丟棄。
        因為隊列滿了,路由器是drop tail原則。
【5】原文為due to the inability of the router to handle small bursts.
        稱“small”,是因為突然的流不大,與之相對,也有大突發。
【6】這兩個階段,發送窗口的增長速率是很不同的,慢啟動階段窗口
        指數增長,如果過早的轉移到擁塞避免階段,此時窗口不夠大,
        又是線性增長,會在很長一段時間內拖慢吞吐率。
【7】例如, http://tools.ietf.org/html/rfc2309 的主動隊列管理算法。
【8】這種情況下,起始窗口較大,而增長被限制,初始發送不會丟包,
        也提高了速率。存疑。   
【9】“浪費”帶寬的說法沒有完全理解,既然是丟棄,無論有多少段擁塞鏈路,
        都是一樣的吧。
【10】來自於作者的郵件交流。
 
http://blog.csdn.net/zhangskd/article/details/8200048

引言 

 

TCP中有擁塞控制,也有流控制,它們各自有什么作用呢?

擁塞控制(Congestion Control) — A mechanism to prevent a TCP sender from overwhelming the network.

流控制(Flow Control) — A mechanism to prevent a TCP sender from overwhelming a TCP receiver.

 

下面是一段關於流控制原理的簡要描述。

“The basic flow control algorithm works as follows: The receiver communicates to the sender the maximum

amount of data it can accept using the rwnd protocol field. This is called the receive window. The TCP sender

then sends no more than this amount of data across the network. The TCP sender then stops and waits for

acknowledgements back from the receiver. When acknowledgement of the previously sent data is returned to

the sender, the sender then resumes sending new data. It's essentially the old maxim hurry up and wait. ”

由於發送速度可能大於接收速度、接收端的應用程序未能及時從接收緩沖區讀取數據、接收緩沖區不夠大不能

緩存所有接收到的報文等原因,TCP接收端的接收緩沖區很快就會被塞滿,從而導致不能接收后續的數據,發送端

此后發送數據是無效的,因此需要流控制。TCP流控制主要用於匹配發送端和接收端的速度,即根據接收端當前的

接收能力來調整發送端的發送速度。

 

TCP流控制中一個很重要的地方就是,TCP接收緩存大小是如何動態調整的,即TCP確認窗口上限是如何動態調整的?

本文主要分析TCP接收緩存大小動態調整的原理和實現。

 

原理

 

早期的TCP實現中,TCP接收緩存的大小是固定的。隨着網絡的發展,固定的TCP接收緩存值就不適應了,

成為TCP性能的瓶頸之一。這時候就需要手動去調整,因為不同的網絡需要不同大小的TCP接收緩存,手動調整不僅

費時費力,還會引起一些問題。TCP接收緩存設置小了,就不能充分利用網絡。而TCP緩存設置大了,又浪費了內存。

如果把TCP接收緩存設置為無窮大,那就更糟糕了,因為某些應用可能會耗盡內存,使其它應用的連接陷入飢餓。

所以TCP接收緩存的大小需要動態調整,才能達到最佳的效果。

動態調整TCP接收緩存大小,就是使TCP接收緩存按需分配,同時要確保TCP接收緩存大小不會成為傳輸的限制。

linux采用Dynamic Right-Sizing方法來動態調整TCP的接收緩存大小,其基本思想就是:通過估算發送方的擁塞窗口

的大小,來動態設置TCP接收緩存的大小。

 

It has been demomstrated that this method can successfully grow the receiver's advertised window at a pace

sufficient to avoid constraining the sender's throughput. As a result, systems can avoid the network performance

problems that result from either the under-utilization or over-utilization of buffer space.

 

實現

 

下文代碼基於3.2.12內核,主要源文件為:net/ipv4/tcp_input.c。

[java]  view plain copy
 
  1. struct tcp_sock {  
  2.     ...  
  3.     u32 rcv_nxt; /* What we want to receive next,希望接收的下一個序列號 */  
  4.     u32 rcv_wnd; /* Current receiver window,當前接收窗口的大小*/  
  5.     u32 copied_seq; /* Head of yet unread data,應用程序下次從這里復制數據 */  
  6.     u16 advmss; /* Advertised MSS,接收端通告的MSS */  
  7.     u32 window_clamp; /* Maximal window to advertise,通告窗口的上限*/  
  8.   
  9.     /* Receiver side RTT estimation */  
  10.     struct {  
  11.         u32 rtt;  
  12.         u32 seq;  
  13.         u32 time;  
  14.     } rcv_rtt_est; /* 用於接收端的RTT測量*/  
  15.   
  16.     /* Receiver queue space */  
  17.     struct {  
  18.         int space;  
  19.         u32 seq;  
  20.         u32 time;  
  21.     } rcvq_space; /* 用於調整接收緩沖區和接收窗口*/  
  22.   
  23.     /* Options received (usually on last packet, some only on SYN packets). */  
  24.     struct tcp_options_received rx_opt; /* TCP選項*/  
  25.     ...  
  26. };  
  27.   
  28. struct sock {  
  29.     ...  
  30.     int sk_rcvbuf; /* TCP接收緩沖區的大小*/  
  31.     int sk_sndbuf; /* TCP發送緩沖區大小*/  
  32.     unsigned int ...  
  33.         sk_userlocks : 4, /*TCP接收緩沖區的鎖標志*/  
  34.     ...  
  35. };   

 

RTT測量

 

在發送端有兩種RTT的測量方法(具體可見前面blog),但是因為TCP流控制是在接收端進行的,所以接收端也需要

有測量RTT的方法。

 

(1)沒有時間戳時的測量方法

[java]  view plain copy
 
  1. static inline void tcp_rcv_rtt_measure(struct tcp_sock *tp)  
  2. {  
  3.     /* 第一次接收到數據時,需要對相關變量初始化*/  
  4.     if (tp->rcv_rtt_est.time == 0)  
  5.         goto new_measure;  
  6.   
  7.     /* 收到指定的序列號后,才能獲取一個RTT測量樣本*/  
  8.     if (before(tp->rcv_nxt, tp->rcv_rtt_est.seq))  
  9.         return;  
  10.   
  11.     /* RTT的樣本:jiffies - tp->rcv_rtt_est.time */  
  12.     tcp_rcv_rtt_update(tp, jiffies - tp->rcv_rtt_est.time, 1);  
  13.   
  14. new_measure:  
  15.     tp->rcv_rtt_est.seq = tp->rcv_nxt + tp->rcv_wnd; /* 收到此序列號的ack時,一個RTT樣本的計時結束*/  
  16.     tp->rcv_rtt_est.time = tcp_time_stamp; /* 一個RTT樣本開始計時*/  
  17. }  

此函數在接收到帶有負載的數據段時被調用。

此函數的原理:我們知道發送端不可能在一個RTT期間發送大於一個通告窗口的數據量。那么接收端可以把接收一個

確認窗口的數據量(rcv_wnd)所用的時間作為RTT。接收端收到一個數據段,然后發送確認(確認號為rcv_nxt,通告

窗口為rcv_wnd),開始計時,RTT就是收到序號為rcv_nxt + rcv_wnd的數據段所用的時間。

很顯然,這種假設並不准確,測量所得的RTT會偏大一些。所以這種方法只有當沒有采用時間戳選項時才使用,而內核

默認是采用時間戳選項的(tcp_timestamps為1)。

下面是一段對此方法的評價:

If the sender is being throttled by the network, this estimate will be valid. However, if the sending application did not

have any data to send, the measured time could be much larger than the actual round-trip time. Thus this measurement

acts only as an upper-bound on the round-trip time.

 

(2)采用時間戳選項時的測量方法

[java]  view plain copy
 
  1. static inline void tcp_rcv_rtt_measure_ts(struct sock *sk, const struct sk_buff *skb)  
  2. {  
  3.     struct tcp_sock *tp = tcp_sk(sk);  
  4.     /* 啟用了Timestamps選項,並且流量穩定*/  
  5.     if (tp->rx_opt.rcv_tsecr && (TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq >=  
  6.         inet_csk(sk)->icsk_ack.rcv_mss))  
  7.         /* RTT = 當前時間 - 回顯時間*/  
  8.         tcp_rcv_rtt_update(tp, tcp_time_stamp - tp->rx_opt.rcv_tsecr, 0);  
  9. }  

雖然此種方法是默認方法,但是在流量小的時候,通過時間戳采樣得到的RTT的值會偏大,此時就會采用

沒有時間戳時的RTT測量方法。

 

(3)采樣處理

不管是沒有使用時間戳選項的RTT采樣,還是使用時間戳選項的RTT采樣,都是獲得一個RTT樣本。

之后還需要對獲得的RTT樣本進行處理,以得到最終的RTT。

[java]  view plain copy
 
  1. /* win_dep表示是否對RTT采樣進行微調,1為不進行微調,0為進行微調。*/  
  2. static void tcp_rcv_rtt_update(struct tcp_sock *tp, u32 sample, int win_dep)  
  3. {  
  4.     u32 new_sample = tp->rcv_rtt_est.rtt;  
  5.     long m = sample;  
  6.   
  7.     if (m == 0)  
  8.         m = 1; /* 時延最小為1ms*/  
  9.   
  10.     if (new_sample != 0) { /* 不是第一次獲得樣本*/  
  11.         /* If we sample in larger samples in the non-timestamp case, we could grossly 
  12.          * overestimate the RTT especially with chatty applications or bulk transfer apps 
  13.          * which are stalled on filesystem I/O. 
  14.          * 
  15.          * Also, since we are only going for a minimum in the non-timestamp case, we do 
  16.          * not smooth things out else with timestamps disabled convergence takes too long. 
  17.          */  
  18.         /* 對RTT采樣進行微調,新的RTT樣本只占最終RTT的1/8 */  
  19.         if (! win_dep) {   
  20.             m -= (new_sample >> 3);  
  21.             new_sample += m;  
  22.   
  23.         } else if (m < new_sample)  
  24.             /* 不對RTT采樣進行微調,直接取最小值,原因可見上面那段注釋*/  
  25.             new_sample = m << 3;   
  26.   
  27.     } else {   
  28.         /* No previous measure. 第一次獲得樣本*/  
  29.         new_sample = m << 3;  
  30.     }  
  31.   
  32.     if (tp->rcv_rtt_est.rtt != new_sample)  
  33.         tp->rcv_rtt_est.rtt = new_sample; /* 更新RTT*/  
  34. }  

對於沒有使用時間戳選項的RTT測量方法,不進行微調。因為用此種方法獲得的RTT采樣值已經偏高而且收斂

很慢。直接選擇最小RTT樣本作為最終的RTT測量值。

對於使用時間戳選項的RTT測量方法,進行微調,新樣本占最終RTT的1/8,即rtt = 7/8 old + 1/8 new。

 

調整接收緩存

 

當數據從TCP接收緩存復制到用戶空間之后,會調用tcp_rcv_space_adjust()來調整TCP接收緩存和接收窗口上限的大小。

[java]  view plain copy
 
  1. /*  
  2.  * This function should be called every time data is copied to user space. 
  3.  * It calculates the appropriate TCP receive buffer space. 
  4.  */  
  5. void tcp_rcv_space_adjust(struct sock *sk)  
  6. {  
  7.     struct tcp_sock *tp = tcp_sk(sk);  
  8.     int time;  
  9.     int space;  
  10.   
  11.     /* 第一次調整*/  
  12.     if (tp->rcvq_space.time == 0)  
  13.         goto new_measure;  
  14.   
  15.     time = tcp_time_stamp - tp->rcvq_space.time; /*計算上次調整到現在的時間*/  
  16.   
  17.     /* 調整至少每隔一個RTT才進行一次,RTT的作用在這里!*/  
  18.     if (time < (tp->rcv_rtt_est.rtt >> 3) || tp->rcv_rtt_est.rtt == 0)  
  19.         return;  
  20.   
  21.     /* 一個RTT內接收方應用程序接收並復制到用戶空間的數據量的2倍*/  
  22.     space = 2 * (tp->copied_seq - tp->rcvq_space.seq);  
  23.     space = max(tp->rcvq_space.space, space);  
  24.   
  25.     /* 如果這次的space比上次的大*/  
  26.     if (tp->rcvq_space.space != space) {  
  27.         int rcvmem;  
  28.         tp->rcvq_space.space = space; /*更新rcvq_space.space*/  
  29.   
  30.         /* 啟用自動調節接收緩沖區大小,並且接收緩沖區沒有上鎖*/  
  31.         if (sysctl_tcp_moderate_rcvbuf && ! (sk->sk_userlocks & SOCK_RCVBUF_LOCK)) {  
  32.             int new_clamp = space;  
  33.             /* Receive space grows, normalize in order to take into account packet headers and 
  34.              * sk_buff structure overhead. 
  35.              */  
  36.              space /= tp->advmss; /* 接收緩沖區可以緩存數據包的個數*/  
  37.   
  38.              if (!space)  
  39.                 space = 1;  
  40.   
  41.             /* 一個數據包耗費的總內存包括: 
  42.                * 應用層數據:tp->advmss, 
  43.                * 協議頭:MAX_TCP_HEADER, 
  44.                * sk_buff結構, 
  45.                * skb_shared_info結構。 
  46.                */  
  47.              rcvmem = SKB_TRUESIZE(tp->advmss + MAX_TCP_HEADER);  
  48.   
  49.              /* 對rcvmem進行微調*/  
  50.              while(tcp_win_from_space(rcvmem) < tp->advmss)  
  51.                  rcvmem += 128;  
  52.   
  53.              space *= rcvmem;  
  54.              space = min(space, sysctl_tcp_rmem[2]); /*不能超過允許的最大接收緩沖區大小*/  
  55.   
  56.              if (space > sk->sk_rcvbuf) {  
  57.                  sk->sk_rcvbuf = space; /* 調整接收緩沖區的大小*/  
  58.                  /* Make the window clamp follow along. */  
  59.                  tp->window_clamp = new_clamp; /*調整接收窗口的上限*/  
  60.              }  
  61.         }  
  62.     }  
  63.   
  64. new_measure:  
  65.      /*此序號之前的數據已復制到用戶空間,下次復制將從這里開始*/  
  66.     tp->rcvq_space.seq = tp->copied_seq;  
  67.     tp->rcvq_space.time = tcp_time_stamp; /*記錄這次調整的時間*/  
  68. }  
  69.   
  70.   
  71. /* return minimum truesize of the skb containing X bytes of data */  
  72. #define SKB_TRUESIZE(X) ((X) +              \  
  73.                             SKB_DATA_ALIGN(sizeof(struct sk_buff)) +        \  
  74.                             SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))  
  75.   
  76.   
  77. static inline int tcp_win_from_space(int space)  
  78. {  
  79.     return sysctl_tcp_adv_win_scale <= 0 ?  
  80.               (space >> (-sysctl_tcp_adv_win_scale)) :  
  81.                space - (space >> sysctl_tcp_adv_win_scale);  
  82. }  

tp->rcvq_space.space表示當前接收緩存的大小(只包括應用層數據,單位為字節)。

sk->sk_rcvbuf表示當前接收緩存的大小(包括應用層數據、TCP協議頭、sk_buff和skb_shared_info結構,

tcp_adv_win_scale微調,單位為字節)。

 

系統參數

(1) tcp_moderate_rcvbuf

是否自動調節TCP接收緩沖區的大小,默認值為1。

(2) tcp_adv_win_scale

在tcp_moderate_rcvbuf啟用的情況下,用來對計算接收緩沖區和接收窗口的參數進行微調,默認值為2。

This means that the application buffer is 1/4th of the total buffer space specified in the tcp_rmem variable.

(3) tcp_rmem

包括三個參數:min default max。

tcp_rmem[1] — default :接收緩沖區長度的初始值,用來初始化sock的sk_rcvbuf,默認為87380字節。

tcp_rmem[2] — max:接收緩沖區長度的最大值,用來調整sock的sk_rcvbuf,默認為4194304,一般是2000多個數據包。 

 

小結:接收端的接收窗口上限和接收緩沖區大小,是接收方應用程序在上個RTT內接收並復制到用戶空間的數據量的2倍,

並且接收窗口上限和接收緩沖區大小是遞增的。

(1)為什么是2倍呢?

In order to keep pace with the growth of the sender's congestion window during slow-start, the receiver should

use the same doubling factor. Thus the receiver should advertise a window that is twice the size of the last

measured window size.

這樣就能保證接收窗口上限的增長速度不小於擁塞窗口的增長速度,避免接收窗口成為傳輸瓶頸。

(2)收到亂序包時有什么影響?

Packets that are received out of order may have lowered the goodput during this measurement, but will increase

the goodput of the following measurement which, if larger, will supercede this measurement. 

亂序包會使本次的吞吐量測量值偏小,使下次的吞吐量測量值偏大。

 

Author

 

zhangskd @ csdn

 

Reference

 

[1] Mike Fisk, Wu-chun Feng, "Dynamic Right-Sizing in TCP".

 

上傳壓死下載 & 常見TCP選項

http://blog.csdn.net/zhangskd/article/details/7978582
 
 

上傳壓死下載

 

下載文件的速度非常低。

抓取數據包分析,發現:

服務器 —> 客戶端 的包,經歷時間 < 1ms后,對方做出反應。

客戶端 —> 服務器 的包,經歷幾十至幾百ms后,對方才有反應。一個文件中的第一個SYN請求還超時,3s后重傳。

服務器 —> 客戶端 的包,至少都重傳了一遍,不論是在建立連接時,還是在傳送數據時。

可能的原因:客戶端 —> 服務器的鏈路擁塞,丟包率高,客戶端的ACK丟失了,服務器就會超時重傳。

 

標准TCP的實現借助反饋機制(ACK數據包)來控制流量。當鏈路上行方向帶寬用滿后,下行方向數據的ACK數據包

將與上行方向的大數據流競爭上行帶寬,丟包的概率增大。ACK數據包的丟失將嚴重影響TCP的流控機制,從而降低

下行方向的數據吞吐率,造成下行帶寬的嚴重浪費,即所謂的上傳壓死下載。

根本上的解決方法:杜絕上行擁塞,並給予ACK數據包高優先級。

 

更具體的猜測:

P2P軟件在下載的同時,也在為其他用戶提供上傳。BT、迅雷等軟件在下載的同時又作為種子為其他人提供下載服務,

由於ADSL上行帶寬最大只有512Kbps,所以使用P2P軟件后更容易造成局域網出口上行帶寬的擁塞,但是任何上網操作

均需要上行/下行兩個方向的流量,如果上行帶寬被占滿,就會影響到下行帶寬的使用。

 

常見TCP SYN包選項構成

 

02 04 05 b4

04 02 08 0a

00 00 ed d1

00 00 00 00

0103 03 06

 

Maximum Segment Size

0x 02 04 05 b4

Kind = 2

Length = 4

Maximum Segment Size = 0x05b4 = 1460

 

SACK permitted

0x 04 02

Kind = 4

Length = 2

 

Timestamp

0x 08 0a 00 00 ed d1 00 00 00 00

Kind = 8

Length = 10

Timestamp Value = 0x0000edd1 = 60881

Timestamp Echo Reply = 0x00000000 = 0

 

NOP

0x 01

Kind = 1

 

Window Scale

0x 03 03 06

Kind  = 3

Length = 3

Shift count = 6

TCP頭中Window size value為14600,表示tcp_rmem為14600字節。接下來的數據包中,這個值就要擴大64倍。


免責聲明!

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



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