天天看點

TCP協定-TCP的流量控制

一、前言

一般來說,我們總是希望資料傳輸能盡可能快一點。但如果發送方把資料發送得過快的話,接收方就可能來不及接收,這就會造成資料的丢失。而TCP的流量控制機制就是為了解決這個端到端的資料傳輸速率問題。

所謂流量控制就是根據接收方的實際接收能力,來控制發送方的資料發送速率。進而讓發送方的發送速率不要太快,要讓接收方來得及接收。

流量控制解決的是一個端到端的問題,是接收端控制發送端發送資料的速率,以便使接收端來得及接收。

TCP協定使用滑動視窗機制來實作對發送方的流量控制。

二、滑動視窗機制

 2.1 利用滑動視窗可以提高傳輸速度

        我們知道,TCP是以封包段為機關發送資料的,如果每發一個段就進行一次确認應答處理的話,這樣的傳輸方式有一個缺點。那就是,包的往返時間越長通信性能就越低。為了解決這個問題,TCP引入了視窗這個概念。即使在往返時間較長的情況下,它也能控制TCP網絡通信性能的下降。

        具體是怎麼做的呢?

        為了便于說明滑動視窗機制的工作原理,我們假定資料傳輸隻在一個方向上進行,A(發送方) —> B(接收方),即 A 發送資料,B 接收資料并給出确認。

        發送方發送資料時不再是一次隻發送一個TCP封包段,而是一次連續發送多個封包段。也就是說,發送方在發送了一個段之後不必一直等待這個段的确認應答,而是繼續發送下一個封包段。每當收到一個封包段的确認應答後,視窗就向前滑動一個封包段的長度,因為已發送并收到确認應答的封包段不需要再保留在視窗中了,但已發送還未收到确認應答的段還必須保留在視窗中,以便在逾時重傳時使用。需要注意的是,滑動視窗是以位元組為機關向前滑動的。

示例:假設一個TCP封包段包含有1000位元組的資料(注意:不包含TCP首部長度),發送端一次發送4個封包段。如下圖所示:

TCP協定-TCP的流量控制

 說明:滑動視窗的大小是4個封包段的資料部分長度,即4000位元組。這裡的視窗大小就是指無需等待确認應答而可以繼續發送資料的最大值。

        當主機A收到第1個封包段的确認應答後(收到主機B發來的确認号1001),即序号 1 ~ 1000 的資料已被确認,此時滑動視窗就可以向前移動1000位元組的長度,此時視窗的左邊界序号是1001,右邊界序号是5001。右邊界 - 左邊界 = 視窗大小。

        當主機A收到第2個封包段的确認應答後(收到主機B發來的确認号2001),即序号 1001 ~ 2000 的資料已被确認,此時滑動視窗繼續向前移動1000位元組的長度,此時視窗的左邊界序号是2001,右邊界序号是6001。

TCP協定-TCP的流量控制

 分析:視窗以外的部分中,視窗左邊部分表示已發送且已收到确認應答。這些資料顯然不需要再保留了。視窗右邊部分表示不允許發送的資料。因為接收方都沒有為這部分資料保留臨時存放的緩存空間。

 可以發現,發送視窗的位置由視窗左邊界和右邊界共同确定。發送視窗左邊界每次都是滑動到收到确認應答号的位置上,而發送視窗右邊界則是不允許發送資料的第1個位元組序号。

這種可以順序地将多個封包段同時發送以提高TCP通信性能的機制,被稱為滑動視窗機制。

2.2 發送視窗與接收視窗

上面所講的内容是從發送方的角度來解釋滑動視窗機制的。發送方的視窗稱為發送視窗,而接收方也有一個視窗,稱為接收視窗。

發送視窗,表示的是發送方一次可以連續發送出去的資料量,以位元組為機關。凡是已經發送過的資料,在未收到确認之前都必須暫時保留在發送視窗中,以便在逾時重傳時使用。發送視窗内的資料都是尚未收到确認應答的。

接收視窗,表示的是接收方一次能夠連續接收的資料量,以位元組為機關。接收視窗内的資料都是允許被接收的。

需要注意的是,發送視窗值不是由發送方自己決定的,而是根據接收方給出的視窗值,發送方再構造出自己的發送視窗值。

在TCP封包段的首部結構中,有一個“視窗”字段,其作用是告訴對方本端的接收視窗的大小,以便控制發送方的發送視窗大小。

二者的關系:發送視窗值 ≤ 接收視窗值

此外,發送方的發送視窗大小還要受到目前網絡擁塞程度的制約。我們暫時不考慮網絡擁塞的影響,後續講述TCP擁塞控制時再讨論。

為了便于下面的解釋說明,我們假定一個TCP封包段的資料部分長度為1位元組,資料傳輸方向為:A(發送方) —> B(接收方)

假定發送方A的發送視窗大小為20位元組,接收方B的接收視窗也為20位元組。

現在假定A發送了序号為 31~41 的資料。這時,發送視窗位置并未改變,但發送視窗内靠後面的有11位元組(黑色小方框表示)表示已發送但未收到确認。而發送視窗内靠前面的9個位元組(42~50)是允許發送但尚未發送的。如下圖所示:

TCP協定-TCP的流量控制

從上圖可以看出,要描述一個發送視窗的狀态需要三個指針:P1,P2 和 P3。指針都指向位元組的序号。這三個指針指向的幾個部分的意義如下:

  • P3 - P1 = A的發送視窗
  • P2 - P1 = 已發送但尚未收到确認的位元組數
  • P3 - P2 = 允許發送但目前尚未發送的位元組數(又稱為可用視窗或有效視窗)

同時,可以看到,發送方的包可以分為四種狀态:

  • 已發送并收到确認的包
  • 已發送但尚未收到确認的包
  • 允許發送但尚未發送的包
  • 不允許被發送的包
<說明> 這裡的包指的是TCP封包段,因為TCP是以封包段為機關發送資料的。

        再來看看接收方B的接收視窗。B 的接收視窗大小為20位元組。在接收視窗以外的部分,到序号30為止的資料都是已經發送過确認,并且已經傳遞上層應用了。是以在 B 的接收視窗中可以不用再保留這些資料。接收視窗内的資料(31~50)是允許接收的。在上圖中,B 收到了序号為 32 和 33 的資料。這些資料沒有按序到達,因為序号為 31 的資料沒有收到(也許丢失了,也許滞留在網絡中的某處)。請注意,接收方 B 隻能對按序收到的資料中的最高序号給出确認應答,是以 B 發送的确認封包段中的确認号仍為 31(即期望下一次收到的序号),而不能是 32 或是 33,更不能是34。

        現在假定 B 收到了序号為31的資料,并把序号為 31~33的資料傳遞主機,然後B從接收緩存中删除這些資料。接着把接收視窗向前移動3個序号的長度,同時給A發送确認,其中B的接收視窗值仍為20,但确認号為34。這表明 B 已經收到了序号 33 為止的資料。我們注意到,B 還收到了序号為37,38 和 40 的資料,但這些都沒有按序到達,隻能先暫存在接收視窗中。A收到B的确認後,就可以把發送視窗向前滑動3個序号的長度,但指針P2不動。可以看出,現在A的可用視窗增大了,可發送的序号範圍是 42~53,即12個序号的長度,之前是9個序号的長度。

如下圖所示:

TCP協定-TCP的流量控制

A收到新的确認号,發送視窗向前移動

         A 在繼續發送 42 ~ 53 的資料後,指針P2向前移動和指針P3重合,意味着此時可用視窗大小為0。發送視窗内的序号都已用完,但還沒有收到确認(如下圖所示)。由于A的發送視窗已滿,可用視窗已減小到零,是以必須停止發送資料。請注意,存在下面這種可能性:

發送視窗内所有的資料都已正确達到B,B 也早已發出了确認。但不幸的是,所有這些确認都滞留在網絡中了。在沒有收到B的确認時,A 不能猜測:“擷取 B 收到了吧”。為了保證可靠傳輸,A 隻能認為 B 還沒有收到這些資料。于是,A 在經過一段時間後(由逾時計時器控制)就重傳發送視窗中的這部分資料(34 ~ 53),重新設定逾時計時器,直到收到B的确認為止。如果 A 收到的确認号落在了發送視窗内,那麼A就可以使發送視窗繼續向前滑動,并發送新的資料。此時,指針P1指向的位置就是A收到的确認号的位置。
TCP協定-TCP的流量控制

發送視窗内的序号都屬于已發送但未被确認

滑動視窗總結

1、TCP首部中的視窗字段指出了現在運作對方發送的資料量。視窗值是經常動态變化者的。

2、TCP 使用滑動視窗機制。發送視窗裡面的序号表示允許發送的序号。發送視窗左邊的部分表示已發送且已收到了确認應答,而發送視窗右邊的部分表示不允許發送。發送視窗右邊界的變化有兩種情況:一是不動(沒有收到新的确認應答);二是前移(收到了新的确認應答)。發送視窗通常是不斷向前移動的。

2.3 發送緩存與接收緩存,以及視窗與緩存的關系

緩存也就是緩沖區。

發送緩存:發送方的緩沖區用于存儲已經準備就緒的資料和已經發送但尚未收到确認的資料。

接收緩存:接收方緩沖區用于存儲已經被接收但是還沒有被使用者程序消費的資料和未按序到達的資料(這部分資料不能被使用者程序消費)。

發送方的應用程序把位元組流資料寫入 TCP 的發送緩存,而接收方的應用程序則從 TCP 的接收緩存中讀取位元組流資料。

下圖是緩存和視窗的關系:

圖中畫出了發送方維持的發送緩存和發送視窗,以及接收方維持的接收緩存和接收視窗。

TCP協定-TCP的流量控制

TCP 的緩存和視窗的關系

1、發送方的情況

由上圖 (a) 可以看到,發送緩存用來暫時存放:

(1)發送應用程式傳送給發送方TCP準備發送的資料。

(2)TCP已發送但尚未收到确認的資料(圖中黑色部分)。

        發送視窗通常隻是發送緩存的一部分。已被确認的資料應當從發送緩存中删除,是以發送緩沖和發送視窗的左邊界是重合的。發送應用程式最後寫入發送緩存的位元組序号減去最後被确認的位元組序号,就是還保留在發送緩沖中的被寫入的位元組數。發送應用程式必須控制寫入發送緩沖的速率,不能太快,否則發送緩存就沒有存放資料的空間了。

2、接收方的情況

由上圖 (b) 可以看到,接收緩存用來暫時存放:

(1)按序達到的、但尚未被接收應用程式讀取的資料。

(2)未按序到達的資料。

        接收視窗通常也是接收緩存的一部分。如果收到的封包段被檢測出有差錯,則要丢棄。如果接收應用程式來不及讀取收到的資料,接收緩存最終會被填滿,使接收視窗減小到零。反之,如果接收應用程式能夠及時從接收緩存中讀取收到的資料,接收視窗就可以增大,但最大不能超過接收緩存的大小。圖 (b) 中還指出了下一個期望收到的位元組序号。這個位元組序号也就是接收方回複給發送方的确認封包段的首部中的确認号。

根據以上的讨論,我們還需要強調以下三點:

第一,雖然發送方的發送視窗是根據接收方的接收視窗設定的,但在同一時刻,發送方的發送視窗并不總是和 接收方的接收視窗一樣大。這是因為通過網絡傳送視窗值需要經曆一定的時間滞後(這個時間是不确定的)。另外,發送方還可能根據目前網絡的擁塞情況适當減小自己的發送視窗數值。本文前面已經對此說明了發送視窗和接收視窗值之間的關系。

第二,對于不按序到達的資料應如何處理,TCP标準并無明确規定。如果接收方把不按序到達的資料一律丢棄,那麼接收視窗的管理将會比較簡單,但這樣做對網絡資源的利用不利(因為發送方會重複傳送較多的資料)。是以 TCP 通常對不按序到達的資料是先臨時存放在接收視窗中,等到位元組流中所缺少的位元組收到後,再按序傳遞上層的應用程序。

第三,TCP 要求接收方必須有累計确認的功能,這樣可以減少傳輸開銷,同時也能提高傳輸效率。接收方可以在合适的時候發送确認,也可以在自己有資料要發送時把确認資訊順便捎帶上。但請注意兩點:一是接收方不應過分推遲發送确認,否則就會導緻發送方不必要的重傳,這反而浪費了網絡的資源。TCP标準規定,确認推遲的時間不應超過 0.5 秒。若收到一連串具有最大長度的封包段(即封包段長度為MSS),則必須每隔一個封包段就發送一個确認 [RFC 1122]。二是捎帶确認實際上并不經常發生,因為大多數應用程式很少同時在兩個方向上發送資料。

        最後再強調一下,TCP 的通信是全雙工通信。通信中的每一方都在發送和接收封包段。是以,每一方都有自己的發送視窗和結束視窗。在談到這些視窗時,一定要弄清楚是哪一方的視窗。

三、TCP的流量控制

        上面的部分我們講了滑動視窗機制,知道了滑動視窗機制可以提高TCP通信的傳輸速率,同時利用滑動視窗機制還可以很友善地實作對TCP的流量控制。

        我們需要弄清楚,所謂TCP的流量控制,它控制的對象是誰?解決的是什麼問題?TCP控制的對象是發送方,解決的問題是控制發送方的資料發送速率問題。接下來我們就要弄清楚,TCP是如何利用滑動視窗機制來解決這個流量控制問題的?

        下面我們通過一個例子來說明如何利用滑動視窗機制進行流量控制。

假設,資料傳輸方向是 A —> B。在三次握手建立連接配接時,B 告訴了 A:“我的接收視窗是 rwnd=400”(這裡rwnd 表示 receiver window)。是以,發送方的發送視窗不能超過接收方給出的接收視窗的數值。請注意,TCP 的視窗大小的機關是位元組,而不是TCP封包段。再假設每一個封包段為 100 位元組長度,而資料封包段的序号的初始值設為 1(即下圖中序号 seq=1)。請注意,下圖中箭頭上面的大寫ACK表示首部中的确認位ACK,小寫ack表示确認字段的數值。如下圖所示:

TCP協定-TCP的流量控制

        從上圖中我們可以看到,接收方的主機 B 進行了三次流量控制。第一次把接收視窗減小到 rwnd = 300,第二次又減小到 rwnd = 100,最後減小到 rwnd = 0,即不允許發送發再發送資料了。這種使發送方暫停發送的狀态将一直持續到接收方主機 B 重新發出一個新的視窗值為止。我們還應注意到,接收方 B 向 發送方 A 發送的三個封包段都設定了 ACK=1,這是因為隻有在 ACK=1 時确認号字段才有意義。

        現在我們考慮一種情況。在上圖 5-22 中,B 向 A 發送了零視窗的封包段後不久,B 的接收緩存又有了一些存儲空間。于是 B 向 A 發送了 rwnd=400 的封包段。然而這個封包段在傳送過程中丢失了。A 一直等待收到 B 發送的非零視窗的通知,而 B 也一直在等待 A 發送的資料。如果沒有其他措施,這種互相等待的死鎖僵局将一直延續下去。

        為了解決這個問題,TCP 為每一個連接配接設有一個持續計時器(persistence timer)。隻要TCP 連接配接的一方接收到對方的零視窗通知,就啟動持續計時器。若持續計時器設定的逾時時間到期,就發送一個零視窗探測封包段(僅攜帶一個位元組的資料),而對方就可在确認這個探測封包段時給出現在新的視窗值。如果視窗值仍為零,那麼收到這個封包段的一方就重新設定持續計時器。如果視窗值不為零,那麼死鎖的僵局就可以打破了。

<說明> TCP規定,即使設定為零視窗,也必須接收以下幾種封包段:零視窗探測封包段、确認封包段(ACK=1)和攜帶緊急資料的封包段(URG=1)。

四、TCP的傳輸效率

        前面已經講過,應用程序把資料傳送到 TCP 的發送緩存後,剩下的發送任務就由 TCP 來控制了。可以用不同的機制來控制 TCP 封包段的發送時機。例如,第一種機制是 TCP 維持一個變量,它等于 最大封包段長度MSS。隻要緩存中存放的資料達到MSS位元組時,就組裝成一個 TCP 封包段發送出去。第二種機制是由發送方的應用程序指明要求發送封包段,即 TCP 支援的 推送(push)操作。第三種機制是發送方的一個計時器期限到了,這是就把目前已有的緩存資料裝入封包段(但長度不能超過MSS)發送出去。

        但是,如何控制 TCP 發送封包段的時機仍然是一個較為複雜的問題。

        例如,一個互動式的使用者使用 Telnet連接配接(傳輸層為TCP協定)。假設使用者隻發一個字元,加上20位元組的TCP首部後,得到21位元組長的 TCP 封包段。再加上 20 位元組的 IP 首部,形成 41 位元組長的 IP 資料報。在接收方 TCP 收到資料後立即發出确認,構成的 IP 資料報是40位元組長(假定沒有發送資料)。若使用者要求遠地主機回送這一字元,則又要發回 41 位元組長的 IP 資料報和 40 位元組長的确認IP資料報。這樣,使用者僅發1個字元的資料,線路上就需傳送總長度為 162 位元組共 4 個封包段。當線路帶寬并不富裕時,這種傳送方法的效率的确不高。是以應适當推遲發回确認封包,并盡量使用捎帶确認的方法。

        在TCP的實作中廣泛使用 Nagle(納格)算法。算法如下:

1、若發送應用程序把要發送的資料逐個位元組地送到 TCP 的發送緩存,則發送方就把第一個資料位元組發送出去,把後面到達的資料位元組都先緩存起來。

2、當發送方接收到對第一個資料字元的确認後,再把發送緩存中的所有資料組裝成一個封包段再發送出去,同時繼續對随後到達的資料進行緩存。隻有在收到對前一個封包段的确認後,才繼續發送下一個封包段。

3、當資料到達較快而網絡速率較慢時,用這樣的方法可明顯地減少所用的網絡帶寬。

4、Nagle算法還規定:當到達的資料已達到發送視窗大小的一半或已經達到封包段的最大長度時,就立即發送一個封包段。

        另一個問題叫糊塗視窗綜合征(silly window syndrome)[RFC 813],有時也會使 TCP 的性能變壞。設想一種情況:TCP 接收方的緩存已滿,而互動式的應用程序一次隻從接收緩存中讀取 1 個位元組(這樣就使接收緩存空間僅騰出 1 個位元組),然後向發送方發送确認,并把視窗設定為 1 個位元組(但發送的資料報為 40 位元組長)。接着,發送方又發來 1 個位元組的資料(請注意,發送方的 IP 資料報是 41 位元組長)。接收方發回确認,仍然将視窗設定為 1 個位元組。這樣進行下去,使網絡的傳輸效率很低。

        要解決這個問題,可以讓接收方等待一段時間,使得或者接收緩存已有足夠空間容納一個最長的封包段MSS,或者等到接收方緩存已有一半空閑的空間。隻要出現這兩種情況之一,接收方就發出确認封包,并向發送方通知目前的視窗大小。此外,發送方也不要發送太小的封包段,而是把資料積累成足夠大的封包段,或達到接收方緩存的空間的一半大小。

        上述兩種方法可以配合使用。使得在發送方不發送很小的封包段的同時,接收方也不要在緩存剛剛有了一點小的空間就急忙把這個很小的接收視窗大小資訊通知給發送方。

參考

《計算機網絡(第7版-謝希仁)》第五章

《圖解TCP_IP(第5版)》第六章

暢談linux下TCP(下)

繼續閱讀