研究TCP的擁塞機制,不只僅是想了解TCP如何的精巧,更多的是領悟其設計思想,即在通常狀況下,咱們該怎樣處理問題。
一.擁塞的發生與其不可避免
擁塞發生的主要緣由:在於網絡可以提供的資源不足以知足用戶的需求,這些資源包括緩存空間、鏈路帶寬容量和中間節點的處理能力。因爲互聯網的設計機制致使其缺少「接納控制」能力,所以在網絡資源不足時不能限制用戶數量,而只能靠下降服務質量來繼續爲用戶服務,也就是「盡力而爲」的服務。
擁塞實際上是一個動態問題,咱們沒有辦法用一個靜態方案去解決,從這個意義上來講,擁塞是不可避免的。
- 靜態解決問題辦法1:
- 例如:增長緩存空間到必定程度時,只會加劇擁塞,而不是減輕擁塞,這是由於當數據包通過長時間排隊完成轉發時,它們極可能早已超時,從而引發源端超時重發,而這些數據包還會繼續傳輸到下一路由器,從而浪費網絡資源,加劇網絡擁塞。事實上,緩存空間不足致使的丟包更多的是擁塞的「症狀」而非緣由。另外,增長鏈路帶寬及提升處理能力也不能解決擁塞問題。
- 靜態解決問題辦法2:
- 例如:咱們有四臺主機ABCD鏈接路由器R,全部鏈路帶寬都是1Gbps,若是A和B同時向C以1Gbps的速率發送數據,則路由器R的輸入速率爲2Gbps,而輸出速率只能爲1Gbps,從而產生擁塞。避免擁塞的方法只能是控制AB的速率,例如,都是0.5Gbps,可是,這只是一種狀況,假若D也向R發送數據,且速率爲1Gbps,那麼,咱們先前的修正又是不成立的,
二.流量控制
- 早期的TCP協議只有基於窗口的流控制(flow control)機制,咱們簡單介紹一下,並分析其不足。 在TCP中,爲了實現可靠性,發送方發出一個數據段以後要等待接受方相應的確認信息,而不是直接發送下一個分組。
- 具體的技術是採用滑動窗口,以便通訊雙方可以充分利用帶寬。滑動窗口容許發送方在收到接收方的確認以前發送多個數據段。窗口大小決定了在收到目的地確認以前,一次能夠傳送的數據段的最大數目。窗口大小越大,主機一次能夠傳輸的數據段就越多。當主機傳輸窗口大小數目的數據段後,就必須等收到確認,才能夠再傳下面的數據段。例如,若視窗的大小爲 1,則傳完數據段後,都必須通過確認,才能夠再傳下一個數據段;當窗口大小等於3時,發送方能夠一次傳輸3個數據段,等待對方確認後,再傳輸下面三個數據段。
- 窗口的大小在通訊雙方鏈接期間是可變的,通訊雙方能夠經過協商動態地修改窗口大小。在TCP的每一個確認中,除了指出但願收到的下一個數據段的序列號以外,還包括一個窗口通告,通告中指出了接收方還能再收多少數據段(咱們能夠把通告當作接收緩衝區大小)。若是通告值增大,窗口大小也相應增大;通告值減少,窗口大小也相應減少。可是咱們能夠發現,接收端並無特別合適的方法來判斷當前網絡是否擁塞,由於它只是被動得接收,不像發送端,當發出一個數據段後,會等待對方得確認信息,若是超時,就能夠認爲網絡已經擁塞了。
- 因此,改變窗口大小的惟一根據,就是接收端緩衝區的大小了。
- 流量控制做爲接受方管理髮送方發送數據的方式,用來防止接受方可用的數據緩存空間的溢出。
- 流控制是一種局部控制機制,其參與者僅僅是發送方和接收方,它只考慮了接收端的接收能力,而沒有考慮到網絡的傳輸能力;
- 而擁塞控制則注重於總體,其考慮的是整個網絡的傳輸能力,是一種全局控制機制。正由於流控制的這種侷限性,從而致使了擁塞崩潰現象的發生。
三.重傳
一、一旦收到確認,發送方關閉重發定時器而且將數據片的備份從重發隊列中刪除。發送方若是在規定的時間內沒有收到數據確認,就重傳該數據。
二、當TCP超時並重傳時,它不必定要重傳一樣的報文段,相反,TCP容許進行從新分組而發送一個較大的報文段,這將有助於提升性能(固然,這個較大的報文段不可以超過接收方聲明的MSS)。在協議中這是容許的,由於TCP是使用字節序號而不是報文段序號來進行識別它所要發送的數據和進行確認。
三、重發定時器
(1) 每一次一個包含數據的包被髮送(包括重發),若是該定時器沒有運行則啓動它,使得它在RTO秒以後超時(按照當前的RTO值)。
(2) 當全部的發出數據都被確認以後,關閉該重發定時器。
(3) 當接收到一個ACK確認一個新的數據,從新啓動該重發定時器,使得它在RTO秒以後超時(按照當前的RTO值)
四.TCP擁塞控制機制
TCP的擁塞控制由4個核心算法組成:「慢啓動」(Slow Start)、「擁塞避免」(Congestion voidance)、「快速重傳 」(Fast Retransmit)、「快速恢復」(Fast Recovery)。
-- 慢啓動
早期開發的TCP應用在啓動一個鏈接時會向網絡中發送大量的數據包,這樣很容易致使路由器緩存空間耗盡,網絡發生擁塞,使得TCP鏈接的吞吐量急劇降低。因爲TCP源端一開始並不知道網絡資源當前的利用情況,所以新創建的TCP鏈接不能一開始就發送大量數據,而只能逐步增長每次發送的數據量,以免上述現象的發生,這裏有一個「學習」的過程。 假設client要發送5120字節到server,
慢啓動過程以下:
1.初始狀態,cwnd=1,seq_num=1;client發送第一個segment;
2.server接收到512字節(一個segment),迴應ack_num=513;
3.client接收ack(513),cwnd=1+1=2;如今能夠一次發送2個數據段而沒必要等待ack
4.server接收到2個segment,迴應ack_num=513+512*2=1537
5.client接收ack(1537),cwnd=2+1;一次發送3個數據段
6.server接收到3個segment,迴應2個ack,分別爲ack_num=1537+1024=2561和ack_num=2561+512=3073
7.client接收ack(2561)和ack(3073),cwnd=3+2=5;一次能夠發送5個數據段,可是隻用4個就知足要求了
8.server接收到4個segment,迴應2個ack,分別爲4097,5121 9.已經發送5120字節,任務完成!
總結一下: 當創建新的TCP鏈接時,擁塞窗口(congestion window,cwnd)初始化爲一個數據包大小。源端按cwnd大小發送數據,每收到一個ACK確認,cwnd就增長一個數據包發送量。
-- 擁塞避免
能夠想象,若是按上述慢啓動的邏輯繼續下去而不加任何控制的話,必然會發生擁塞,引入一個慢啓動閾值ssthresh的概念,當cwnd<ssthresh的時候,tcp處於慢啓動狀態,不然,進入擁塞避免階段。一般,ssthresh初始化爲 64 Kbytes。 當cwnd = 64947 + 512 = 65459,進入擁塞避免階段,假設此時seq_num = _101024:
1.client一次發送cwnd,可是先考慮頭兩個segment
2.server迴應ack_num = 102048
3.client接收到ack(102048),cwnd = 65459 + [(512 * 512) /65459] = 65459 + 4 = 65463,也就是說,每接到一個ack,cwnd只增長4個字節。
4.client發送一個segment,並開啓ack timer,等待server對這個segment的ack,若是超時,則認爲網絡已經處於擁塞狀態,則重設慢啓動閥值ssthresh=當前cwnd/2=65463/2=32731,而且,馬上把cwnd設爲1,很極端的處理!
5.此時,cwnd<ssthresh,因此,恢復到慢啓動狀態。
總結一下: 若是當前cwnd達到慢啓動閥值,則試探性的發送一個segment,若是server超時未響應,TCP認爲網絡能力降低,必須下降慢啓動閥值,同時,爲了不形勢惡化,乾脆採起極端措施,把發送窗口降爲1,我的感受應該有更好的方法。
-- 快速重傳和快速恢復
前面講過標準的重傳,client會等待RTO時間再重傳,但有時候,沒必要等這麼久也能夠判斷須要重傳,
快速重傳
例如:client一次發送8個segment,seq_num起始值爲100000,可是因爲網絡緣由,100512丟失,其餘的正常,則server會響應4個ack(100512)(爲何呢,tcp會把接收到的其餘segment緩存起來,ack_num必須是連續的),這時候,client接收到四個重複的ack,它徹底有理由判斷100512丟失,進而重傳,而沒必要傻等RTO時間了。
快速恢復
咱們一般認爲client接收到3個重複的ack後,就會開始快速重傳,可是,若是還有更多的重複ack呢,如何處理?這就是快速恢復要作的,事實上,咱們能夠把快速恢復看做是快速重傳的後續處理,它不是一種單獨存在的形態。
如下是具體的流程:
假設此時cwnd=70000,client發送4096字節到server,也就是8個segment,起始seq_num = _100000:
1.client發送seq_num = _100000
2.seq_num =100512的segment丟失
3.client發送seq_num = _101024
4.server接收到兩個segment,它意識到100512丟失,先把收到的這兩個segment緩存起來
5.server迴應一個ack(100512),表示它還期待這個segment
6.client發送seq_num = _101536
7.server接收到一個segment,它判斷不是100512,依舊把收到的這個segment緩存起來,並回應ack(100512) 。 。 。
8.如下同六、7,直到client收到3個ack(100512),進入快速重發階段:
9.重設慢啓動閥值ssthresh=當前cwnd/2=70000/2=35000
10.client發送seq_num = 100512
如下,進入快速恢復階段:
11.重設cwnd = ssthresh + 3 segments =35000 + 3*512 = 36536,之因此要加3,是由於咱們已經接收到3個ack(100512)了,根據前面說的,每接收到一個ack,cwnd加1
12.client接收到第四個、第五個ack(100512),cwnd=36536+2*512=37560
13.server接收到100512,響應ack_num = _104096 14.此時,cwnd>ssthresh,進入擁塞避免階段。
【思考】:爲何一般clieng每接收到一個ack,會把cwnd增長一個segment呢? 這是基於「管道」模型(pipe model)的「數據包守恆」的原則(conservation of packets principle),即同一時刻在網絡中傳輸的數據包數量是恆定的,只有當「舊」數據包離開網絡後,才能發送「新」數據包進入網絡。若是發送方收到一個ACK,則認爲已經有一個數據包離開了網絡,因而將擁塞窗口加1。若是「數據包守恆」原則可以獲得嚴格遵照,那麼網絡中將不多會發生擁塞;本質上,擁塞控制的目的就是找到違反該原則的地方並進行修正。
五.聯想
想一想看,能不能把TCP解決擁塞的方法應用到交通擁塞呢? 咱們有兩個原則:一是擁塞不可避免,單純增長資源並不能避免擁塞的發生,只能用動態的方法加以解決;二是數據包守恆原則。政府花費很大資金修路,並不能避免堵車,只能從源頭控制,例如首先限制車輛進入主路,根據實際狀況,再慢慢增長每個路口的車流量,可是,當達到一個閥值,增長速度要放緩,並不時探測整個主路的擁堵狀況,若是狀況危急,馬上封閉半個路口,並將車流量降到最低,也就是從新回覆到慢啓動狀態。 呵呵,有趣!