1. 程式人生 > >計算機網路——傳輸層

計算機網路——傳輸層

UDP(User Datagram Protocol):

基於Intenet IP協議(複用/分用、簡單的錯誤校驗)

儘可能的服務(可能丟失、亂序到達)

無連線(不需要握手、每個UDP段獨立於其他)

常用於流媒體應用(容忍丟失、速率敏感)

UDP用於DNS、SNMP

UDP上實現可靠資料傳輸:

在應用層增加可靠性機制

應用特定的錯誤恢復機制

UDP為什麼存在?

1.無需建立連線(減少延遲)

2.實現簡單(無需維護連線狀態)

3.頭部開銷少

4.沒有擁塞控制(應用可更好的控制傳送時間和速率)

 

滑動視窗(silding window)協議實現機制:

從傳輸資料來講,TCP/UDP以及其他協議都可以完成資料的傳輸,從一端傳輸到另外一端,TCP比較出眾的一點就是提供一個可靠的,流控的資料傳輸,所以實現起來要比其他協議複雜的多:

1. Reliability ,提供TCP的可靠性,TCP的傳輸要保證資料能夠準確到達目的地,如果不能,需要能檢測出來並且重新發送資料。

2. Data Flow Control,提供TCP的流控特性,管理髮送資料的速率,不要超過裝置的承載能力。

為了能夠實現以上2點,TCP實現了很多細節的功能來保證資料傳輸,比如說 滑動視窗適應系統,超時重傳機制,累計ACK等。

對於TCP會話的傳送方,任何時候在其傳送快取內的資料都可以分為4類,“已經發送並得到對端ACK的”,

“已經發送但還未收到對端ACK的”,

“未傳送但對端允許傳送的”,

“未傳送且對端不允許傳送”。

“已經發送但還未收到對端ACK的”和“未傳送但對端允許傳送的”這兩部分資料稱之為傳送視窗(中間兩部分)。

對於TCP的接收方,在某一時刻在它的接收快取記憶體在3種。“已接收”,“未接收準備接收”,“未接收並未準備接收”(由於ACK直接由TCP協議棧回覆,預設無應用延遲,不存在“已接收未回覆ACK”)。

其中“未接收準備接收”稱之為接收視窗。

收到ACK=36時,視窗滑動。

滑動視窗實現面向流的可靠性

最基本的傳輸可靠性來源於“確認重傳”機制。

TCP的滑動視窗的可靠性也是建立在“確認重傳”基礎上的。

傳送視窗只有收到對端對於本段傳送視窗內位元組的ACK確認,才會移動傳送視窗的左邊界。

接收視窗只有在前面所有的段都確認的情況下才會移動左邊界。當在前面還有位元組未接收但收到後面位元組的情況下,視窗不會移動,並不對後續位元組確認。以此確保對端會對這些資料重傳。

滑動視窗的流控特性:

TCP的滑動視窗是動態變化的,隨時通過本端TCP剩餘的接收視窗大小控制來對對端的傳送視窗(滑動視窗)流量進行限制。

 

TCP連線建立與關閉:

TCP概述:

點對點:一個傳送方,一個接收方

可靠地、按序的位元組流

流水線機制:TCP擁塞控制和流量控制機制設定視窗大小

傳送方/接收方快取

全雙工

面向連線:通訊雙方在傳送資料之前必須建立連線、連線狀態只在連線兩端維護,沿途結點不維護、連線狀態包括兩臺主機上快取,連線狀態變數,scoket等。

流量控制機制

 

三次握手和四次揮手

SYN:同步標誌

同步序列編號(Synchronize Sequence Numbers)欄有效。該標誌僅在三次握手建立TCP連線時有效。它提示TCP連線的服務端檢查序列編號,該序列編號為TCP連線初始端(一般是客戶端)的初始序列編號。在這裡,可以把TCP序列編號看作是一個範圍從0到4,294,967,295的32位計數器。通過TCP連線交換的資料中每一個位元組都經過序列編號。在TCP報頭中的序列編號欄包括了TCP分段中第一個位元組的序列編號。

ACK:確認標誌
確認編號(Acknowledgement Number)欄有效。大多數情況下該標誌位是置位的。TCP報頭內的確認編號欄內包含的確認編號(w+1,Figure-1)為下一個預期的序列編號,同時提示遠端系統已經成功接收所有資料。

FIN:結束標誌

帶有該標誌置位的資料包用來結束一個TCP回話,但對應埠仍處於開放狀態,準備接收後續資料

對於建連結的3次握手,主要是要初始化Sequence Number 的初始值。通訊的雙方要互相通知對方自己的初始化的Sequence Number(縮寫為ISN:Inital Sequence Number)——所以叫SYN,全稱Synchronize Sequence Numbers。也就上圖中的 x 和 y。這個號要作為以後的資料通訊的序號,以保證應用層接收到的資料不會因為網路上的傳輸的問題而亂序(TCP會用這個序號來拼接資料)。

對於4次揮手,其實你仔細看是2次,因為TCP是全雙工的,所以,傳送方和接收方都需要Fin和Ack。只不過,有一方是被動的,所以看上去就成了所謂的4次揮手。如果兩邊同時斷連線,那就會就進入到CLOSING狀態,然後到達TIME_WAIT狀態。下圖是雙方同時斷連線的示意圖(同樣可以對照著TCP狀態機看):

 

 

TCP重傳機制

TCP要保證所有的資料包都可以到達,所以,必需要有重傳機制。

注意,接收端給傳送端的Ack確認只會確認最後一個連續的包,比如,傳送端發了1,2,3,4,5一共五份資料,接收端收到了1,2,於是回ack 3,然後收到了4(注意此時3沒收到),此時的TCP會怎麼辦?我們要知道,因為正如前面所說的,SeqNum和Ack是以位元組數為單位,所以ack的時候,不能跳著確認,只能確認最大的連續收到的包,不然,傳送端就以為之前的都收到了。

超時重傳機制

一種是不回ack,死等3,當傳送方發現收不到3的ack超時後,會重傳3。一旦接收方收到3後,會ack 回 4——意味著3和4都收到了。

但是,這種方式會有比較嚴重的問題,那就是因為要死等3,所以會導致4和5即便已經收到了,而傳送方也完全不知道發生了什麼事,因為沒有收到Ack,所以,傳送方可能會悲觀地認為也丟了,所以有可能也會導致4和5的重傳。

對此有兩種選擇:

  • 一種是僅重傳timeout的包。也就是第3份資料。
  • 另一種是重傳timeout後所有的資料,也就是第3,4,5這三份資料。

這兩種方式有好也有不好。第一種會節省頻寬,但是慢,第二種會快一點,但是會浪費頻寬,也可能會有無用功。但總體來說都不好。因為都在等timeout,timeout可能會很長(在下篇會說TCP是怎麼動態地計算出timeout的)

快速重傳機制

於是,TCP引入了一種叫Fast Retransmit 的演算法,不以時間驅動,而以資料驅動重傳。也就是說,如果,包沒有連續到達,就ack最後那個可能被丟了的包,如果傳送方連續收到3次相同的ack,就重傳。Fast Retransmit的好處是不用等timeout了再重傳。

比如:如果傳送方發出了1,2,3,4,5份資料,第一份先到送了,於是就ack回2,結果2因為某些原因沒收到,3到達了,於是還是ack回2,後面的4和5都到了,但是還是ack回2,因為2還是沒有收到,於是傳送端收到了三個ack=2的確認,知道了2還沒有到,於是就馬上重轉2。然後,接收端收到了2,此時因為3,4,5都收到了,於是ack回6。示意圖如下:

Fast Retransmit只解決了一個問題,就是timeout的問題,它依然面臨一個艱難的選擇,就是,是重傳之前的一個還是重傳所有的問題。對於上面的示例來說,是重傳#2呢還是重傳#2,#3,#4,#5呢?因為傳送端並不清楚這連續的3個ack(2)是誰傳回來的?也許傳送端發了20份資料,是#6,#10,#20傳來的呢。這樣,傳送端很有可能要重傳從2到20的這堆資料(這就是某些TCP的實際的實現)。可見,這是一把雙刃劍。

 

 

擁塞控制:

TCP通過Sliding Window來做流控(Flow Control),但是TCP覺得這還不夠,因為Sliding Window需要依賴於連線的傳送端和接收端,其並不知道網路中間發生了什麼。TCP的設計者覺得,一個偉大而牛逼的協議僅僅做到流控並不夠,因為流控只是網路模型4層以上的事,TCP的還應該更聰明地知道整個網路上的事。

具體一點,我們知道TCP通過一個timer取樣了RTT並計算RTO,但是,如果網路上的延時突然增加,那麼,TCP對這個事做出的應對只有重傳資料,但是,重傳會導致網路的負擔更重,於是會導致更大的延遲以及更多的丟包,於是,這個情況就會進入惡性迴圈被不斷地放大。試想一下,如果一個網路內有成千上萬的TCP連線都這麼行事,那麼馬上就會形成“網路風暴”,TCP這個協議就會拖垮整個網路。這是一個災難。

所以,TCP不能忽略網路上發生的事情,而無腦地一個勁地重發資料,對網路造成更大的傷害。對此TCP的設計理念是:TCP不是一個自私的協議,當擁塞發生的時候,要做自我犧牲。就像交通阻塞一樣,每個車都應該把路讓出來,而不要再去搶路了。

擁塞控制主要是四個演算法:1)慢啟動2)擁塞避免3)擁塞發生4)快速恢復。這四個演算法不是一天都搞出來的,這個四演算法的發展經歷了很多時間,到今天都還在優化中。 

 

慢熱啟動演算法 – Slow Start

首先,我們來看一下TCP的慢熱啟動。慢啟動的意思是,剛剛加入網路的連線,一點一點地提速,不要一上來就像那些特權車一樣霸道地把路佔滿。新同學上高速還是要慢一點,不要把已經在高速上的秩序給搞亂了。

慢啟動的演算法如下(cwnd全稱Congestion Window):

1)連線建好的開始先初始化cwnd = 1,表明可以傳一個MSS大小的資料。

2)每當收到一個ACK,cwnd++; 呈線性上升

3)每當過了一個RTT,cwnd = cwnd*2; 呈指數讓升

4)還有一個ssthresh(slow start threshold),是一個上限,當cwnd >= ssthresh時,就會進入“擁塞避免演算法”(後面會說這個演算法)

所以,我們可以看到,如果網速很快的話,ACK也會返回得快,RTT也會短,那麼,這個慢啟動就一點也不慢。

 

擁塞避免演算法 – Congestion Avoidance

前面說過,還有一個ssthresh(slow start threshold),是一個上限,當cwnd >= ssthresh時,就會進入“擁塞避免演算法”。一般來說ssthresh的值是65535,單位是位元組,當cwnd達到這個值時後,演算法如下:

1)收到一個ACK時,cwnd = cwnd + 1/cwnd

2)當每過一個RTT時,cwnd = cwnd + 1

這樣就可以避免增長過快導致網路擁塞,慢慢的增加調整到網路的最佳值。很明顯,是一個線性上升的演算法。

 

 擁塞避免演算法 – Congestion Avoidance

前面說過,還有一個ssthresh(slow start threshold),是一個上限,當cwnd >= ssthresh時,就會進入“擁塞避免演算法”。一般來說ssthresh的值是65535,單位是位元組,當cwnd達到這個值時後,演算法如下:

1)收到一個ACK時,cwnd = cwnd + 1/cwnd

2)當每過一個RTT時,cwnd = cwnd + 1

這樣就可以避免增長過快導致網路擁塞,慢慢的增加調整到網路的最佳值。很明顯,是一個線性上升的演算法。

擁塞狀態時的演算法

前面我們說過,當丟包的時候,會有兩種情況:

1)等到RTO超時,重傳資料包。TCP認為這種情況太糟糕,反應也很強烈。

    • sshthresh =  cwnd /2
    • cwnd 重置為 1
    • 進入慢啟動過程

2)Fast Retransmit演算法,也就是在收到3個duplicate ACK時就開啟重傳,而不用等到RTO超時。

    • TCP Tahoe的實現和RTO超時一樣。
    • TCP Reno的實現是:
      • cwnd = cwnd /2
      • sshthresh = cwnd
      • 進入快速恢復演算法——Fast Recovery

上面我們可以看到RTO超時後,sshthresh會變成cwnd的一半,這意味著,如果cwnd<=sshthresh時出現的丟包,那麼TCP的sshthresh就會減了一半,然後等cwnd又很快地以指數級增漲爬到這個地方時,就會成慢慢的線性增漲。我們可以看到,TCP是怎麼通過這種強烈地震盪快速而小心得找到網站流量的平衡點的。

 

快速恢復演算法 – Fast Recovery

TCP Reno

這個演算法定義在RFC5681。快速重傳和快速恢復演算法一般同時使用。快速恢復演算法是認為,你還有3個Duplicated Acks說明網路也不那麼糟糕,所以沒有必要像RTO超時那麼強烈。 注意,正如前面所說,進入Fast Recovery之前,cwnd 和 sshthresh已被更新:

  • cwnd = cwnd /2
  • sshthresh = cwnd

然後,真正的Fast Recovery演算法如下:

  • cwnd = sshthresh  + 3 * MSS (3的意思是確認有3個數據包被收到了)
  • 重傳Duplicated ACKs指定的資料包
  • 如果再收到 duplicated Acks,那麼cwnd = cwnd +1
  • 如果收到了新的Ack,那麼,cwnd = sshthresh ,然後就進入了擁塞避免的演算法了。

如果你仔細思考一下上面的這個演算法,你就會知道,上面這個演算法也有問題,那就是——它依賴於3個重複的Acks。注意,3個重複的Acks並不代表只丟了一個數據包,很有可能是丟了好多包。但這個演算法只會重傳一個,而剩下的那些包只能等到RTO超時,於是,進入了惡夢模式——超時一個視窗就減半一下,多個超時會超成TCP的傳輸速度呈級數下降,而且也不會觸發Fast Recovery演算法了。

通常來說,正如我們前面所說的,SACK或D-SACK的方法可以讓Fast Recovery或Sender在做決定時更聰明一些,但是並不是所有的TCP的實現都支援SACK(SACK需要兩端都支援),所以,需要一個沒有SACK的解決方案。而通過SACK進行擁塞控制的演算法是FACK(後面會講)

TCP New Reno

於是,1995年,TCP New Reno(參見 RFC 6582 )演算法提出來,主要就是在沒有SACK的支援下改進Fast Recovery演算法的——

  • 當sender這邊收到了3個Duplicated Acks,進入Fast Retransimit模式,開發重傳重複Acks指示的那個包。如果只有這一個包丟了,那麼,重傳這個包後回來的Ack會把整個已經被sender傳輸出去的資料ack回來。如果沒有的話,說明有多個包丟了。我們叫這個ACK為Partial ACK。
  • 一旦Sender這邊發現了Partial ACK出現,那麼,sender就可以推理出來有多個包被丟了,於是乎繼續重傳sliding window裡未被ack的第一個包。直到再也收不到了Partial Ack,才真正結束Fast Recovery這個過程

我們可以看到,這個“Fast Recovery的變更”是一個非常激進的玩法,他同時延長了Fast Retransmit和Fast Recovery的過程。