1.端到端的TCP協(xié)議和IP協(xié)議之間的矛盾
端到端的TCP只能看到兩個節(jié)點,那就是自己和對方,它們是看不到任何中間的路徑的?墒荌P網(wǎng)絡卻是一跳一跳的,它們的矛盾之處在于TCP的端到端流量控制必然會導致網(wǎng)絡擁堵。因為每條TCP連接的一端只知道它對端還有多少空間用于接收數(shù)據(jù),它們并不管到達對端的路徑上是否還有這么大的容量,事實上所有連接的這些空間加在一起將瞬間超過IP網(wǎng)絡的容量,因此TCP也不可能按照滑動窗口流量控制機制很理想的運行。
勢必需要一種擁塞控制機制,反應路徑的擁塞情況。
疑難雜癥15:擁塞控制的本質(zhì)
由于TCP是端到端協(xié)議,因此兩端之間的控制范疇屬于流量控制,IP網(wǎng)絡的擁塞會導致TCP分段的丟失,由于TCP看不到中間的路由器,因此這種丟失只會發(fā)生中間路由器,當然兩個端點的網(wǎng)卡或者IP層丟掉數(shù)據(jù)分段也是TCP看不到的。因此擁塞控制必然作用于IP鏈路。事實上我們可以得知,只有在以下情況下?lián)砣刂撇艜鹱饔茫?/p>
a.兩個或兩個以上的連接(其中一個一定要是TCP,另一個可以是任意連接)經(jīng)過同一個路由器或者同一個鏈路時;
b.只有一個TCP連接,然而它經(jīng)過了一個路由器時。
其它情況下是不會擁塞的。因為一個TCP總是希望獨享整條網(wǎng)絡通路,而這對于多個連接而言是不可能的,必須保證TCP的公平性,這樣這種擁塞控制機制才合理。本質(zhì)上,擁塞的原因就是大家都想獨享全部帶寬資源,結(jié)果導致?lián)砣@也是合理的,畢竟TCP看不到網(wǎng)絡的狀態(tài),同時這也決定了TCP的擁塞控制必須采用試探性的方式,最終到達一個足以引起其“反應”的“刺激點”。
擁塞控制需要完成以下兩個任務:1.公平性;2.擁塞之后退出擁塞狀態(tài)。
疑難雜癥16:影響擁塞的因素
我們必須認識到擁塞控制是一個整體的機制,它不偏向于任何TCP連接,因此這個機制內(nèi)在的就包含了公平性。那么影響擁塞的因素都有什么呢?具有諷刺意味的是,起初TCP并沒有擁塞控制機制,正是TCP的超時重傳風暴(一個分段丟失造成后續(xù)的已經(jīng)發(fā)送的分段均被重傳,而這些重傳大多數(shù)是不必要的)加重了網(wǎng)絡的擁塞。因此重傳必然不能過頻,必須把重傳定時器的超時時間設置的稍微長一些,而這一點在單一重傳定時器的設計中得到了加強。除此TCP自身的因素之外,其它所有的擁塞都可以靠擁塞控制機制來自動完成。
另外,不要把路由器想成一種線速轉(zhuǎn)發(fā)設備,再好的路由器只要接入網(wǎng)絡,總是會拉低網(wǎng)絡的總帶寬,因此即使只有一個TCP連接,由于TCP的發(fā)送方總是以發(fā)送鏈路的帶寬發(fā)送分段,這些分段在經(jīng)過路由器的時候排隊和處理總是會有時延,因此最終肯定會丟包的。
最后,丟包的延后性也會加重擁塞。假設一個TCP連接經(jīng)過了N個路由器,前N-1個路由器都能順利轉(zhuǎn)發(fā)TCP分段,但是最后一個路由器丟失了一個分段,這就導致了這些丟失的分段浪費了前面路由器的大量帶寬。
2.擁塞控制的策略
在介紹擁塞控制之前,首先介紹一下?lián)砣翱冢鼘嶋H上表示的也是“可以發(fā)送多少數(shù)據(jù)”,然而這個和接收端通告的接收窗口意義是不一樣的,后者是流量控制用的窗口,而前者是擁塞控制用的窗口,體現(xiàn)了網(wǎng)絡擁塞程度。
擁塞控制整體上分為兩類,一類是試探性的擁塞探測,另一類則是擁塞避免(注意,不是常規(guī)意義上的擁塞避免)。
2.1.試探性的擁塞探測分為兩類,之一是慢啟動,之二是擁塞窗口加性擴大(也就是熟知的擁塞避免,然而這種方式是避免不了擁塞的)。
2.2.擁塞避免方式擁塞控制旨在還沒有發(fā)生擁塞的時候就先提醒發(fā)送端,網(wǎng)絡擁塞了,這樣發(fā)送端就要么可以進入快速重傳/快速恢復或者顯式的減小擁塞窗口,這樣就避免網(wǎng)絡擁塞的一沓糊涂之后出現(xiàn)超時,從而進入慢啟動階段。
2.3.快速重傳和快速恢復。所謂快速重傳/快速恢復是針對慢啟動的,我們知道慢啟動要從1個MSS開始增加擁塞窗口,而快速重傳/快速恢復則是一旦收到3個冗余ACK,不必進入慢啟動,而是將擁塞窗口縮小為當前閥值的一半加上3,然后如果繼續(xù)收到冗余ACK,則將擁塞窗口加1個MSS,直到收到一個新的數(shù)據(jù)ACK,將窗口設置成正常的閥值,開始加性增加的階段。
當進入快速重傳時,為何要將擁塞窗口縮小為當前閥值的一半加上3呢?加上3是基于數(shù)據(jù)包守恒來說的,既然已經(jīng)收到了3個冗余ACK,說明有三個數(shù)據(jù)分段已經(jīng)到達了接收端,既然三個分段已經(jīng)離開了網(wǎng)絡,那么就是說可以在發(fā)送3個分段了,只要再收到一個冗余ACK,這也說明1個分段已經(jīng)離開了網(wǎng)絡,因此就將擁塞窗口加1個MSS。直到收到新的ACK,說明直到收到第三個冗余ACK時期發(fā)送的TCP分段都已經(jīng)到達對端了,此時進入正常階段開始加性增加擁塞窗口。
疑難雜癥17:超時重傳和收到3個冗余ACK后重傳
這兩種重傳的意義是不同的,超時重傳一般是因為網(wǎng)絡出現(xiàn)了嚴重擁塞(沒有一個分段到達,如果有的話,肯定會有ACK的,若是正常ACK,則重置重傳定時器,若是冗余ACK,則可能是個別報文丟失或者被重排序,若連續(xù)3個冗余ACK,則很有可能是個別分段丟失),此時需要更加嚴厲的縮小擁塞窗口,因此此時進入慢啟動階段。而收到3個冗余ACK后說明確實有中間的分段丟失,然而后面的分段確實到達了接收端,這因為這樣才會發(fā)送冗余ACK,這一般是路由器故障或者輕度擁塞或者其它不太嚴重的原因引起的,因此此時擁塞窗口縮小的幅度就不能太大,此時進入快速重傳/快速恢復階段。
疑難雜癥18:為何收到3個冗余ACK后才重傳
這是一種權衡的結(jié)構,收到兩個或者一個冗余ACK也可以重傳,但是這樣的話可能或造成不必要的重傳,因為兩個數(shù)據(jù)分段發(fā)生亂序的可能性不大,超過三個分段發(fā)生亂序的可能性才大,換句話說,如果僅僅收到一個亂序的分段,那很可能被中間路由器重排了,那么另一個分段很可能馬上就到,然而如果連續(xù)收到了3個分段都沒能彌補那個缺漏,那很可能是它丟失了,需要重傳。因此3個冗余ACK是一種權衡,在減少不必要重傳和確實能檢測出單個分段丟失之間所作的權衡。
注意,冗余ACK是不能捎帶的。
疑難雜癥19:乘性減和加性增的深層含義
為什么是乘性減而加性增呢?擁塞窗口的增加受惠的只是自己,而擁塞窗口減少受益的大家,可是自己卻受到了傷害。哪一點更重要呢?我們知道TCP的擁塞控制中內(nèi)置了公平性,恰恰就是這種乘性減實現(xiàn)了公平性。擁塞窗口的1個MSS的改變影響一個TCP發(fā)送者,為了使得自己擁塞窗口的減少影響更多的TCP發(fā)送者- 讓更多的發(fā)送者受益,那么采取了乘性減的策略。
當然,BIC算法提高了加性增的效率,不再一個一個MSS的加,而是一次加比較多的MSS,采取二分查找的方式逐步找到不丟包的點,然后加性增。
疑難雜癥20:TCP連接的傳輸穩(wěn)定狀態(tài)是什么
首先,先說一下發(fā)送端的發(fā)送窗口怎么確定,它取的是擁塞窗口和接收端通告窗口的最小值。然后,我們提出三種發(fā)送窗口的穩(wěn)定狀態(tài):
a.IP互聯(lián)網(wǎng)絡上接收端擁有大窗口的經(jīng)典鋸齒狀
b.IP互聯(lián)網(wǎng)絡上接收端擁有小窗口的直線狀態(tài)
c.直連網(wǎng)絡端點間的滿載狀態(tài)下的直線狀態(tài)
其中a是大多數(shù)的狀態(tài),因為一般而言,TCP連接都是建立在互聯(lián)網(wǎng)上的,而且是大量的,比如Web瀏覽,電子郵件,網(wǎng)絡游戲,F(xiàn)tp下載等等。TCP發(fā)送端用慢啟動或者擁塞避免方式不斷增加其擁塞窗口,直到丟包的發(fā)生,然后進入慢啟動或者擁塞避免階段(要看是由于超時丟包還是由于冗余ACK丟包),此時發(fā)送窗口將下降到1或者下降一半,這種情況下,一般接收端的接收窗口是比較大的,畢竟IP網(wǎng)絡并不是什么很快速的網(wǎng)絡,一般的機器處理速度都很快。
但是如果接收端特別破,處理速度很慢,就會導致其通告一個很小的窗口,這樣的話,即使擁塞窗口再大,發(fā)送端也還是以通告的接收窗口為發(fā)送窗口,這樣就不會發(fā)生擁塞。最后,如果的TCP連接運行在一個直連的兩臺主機上,那么它將獨享網(wǎng)絡帶寬,這樣該TCP的數(shù)據(jù)流在的情況下將填滿網(wǎng)絡管道(我們把網(wǎng)絡管道定義為帶寬和延時的乘積),其實在這種情況下是不存在擁塞的,就像你一個人獨自徘徊在飄雨黃昏的街頭一樣...
2.4.主動的擁塞避免
前面我們描述的擁塞控制方式都是試探性的檢測,然后擁塞窗口被動的進行乘性減,這樣在接收端窗口很大的情況下(一般都是這樣,網(wǎng)絡擁堵,分段就不會輕易到達接收端,導致接收端的窗口大量空置)就可能出現(xiàn)鋸齒形狀的“時間-窗口”圖,類似在一個擁堵的北京X環(huán)上開車,發(fā)送機發(fā)動,車開動,停止,等待,發(fā)動機發(fā)動,車開動...聽聲音也能聽出來。
雖然TCP看不到下面的IP網(wǎng)絡,然而它還是可以通過檢測RTT的變化以及擁塞窗口的變化推算出IP網(wǎng)絡的擁堵情況的。就比方說北京東四環(huán)一家快遞公司要持續(xù)送快遞到西四環(huán),當發(fā)件人發(fā)現(xiàn)貨到時間越來越慢的時候,他會意識到“下班高峰期快到了”...
可以通過持續(xù)觀測RTT的方式來主動調(diào)整擁塞窗口的大小而不是一味的加性增。然而還有更猛的算法,那就是計算兩個差值的乘積:
(當前擁塞窗口-上一次擁塞窗口)x(當前的RTT-上一次的RTT)
如果結(jié)果是正數(shù),則擁塞窗口減少1/8,若結(jié)果是負數(shù)或者0,則窗口增加一個MSS。注意,這回不再是乘性減了,可以看出,減的幅度比乘性減幅度小,這是因為這種擁塞控制是主動的,而不是之前的那種被動的試探方式。在試探方式中,乘性減以一種懲罰的方式實現(xiàn)了公平性,而在這里的主動方式中,當意識到要擁塞的時候,TCP發(fā)送者主動的減少了擁塞窗口,為了對這種自首行為進行鼓勵,采用了小幅減少擁塞窗口的方式。需要注意的是,在擁塞窗口減小的過程中,乘積的前一個差值是負數(shù),如果后一個差值也是負數(shù),那么結(jié)果就是繼續(xù)縮減窗口,直到擁塞緩解或者窗口減少到了一定程度,使得后一個差值成了正數(shù)或者0,這種情況下,其實后一個差值只能變?yōu)?。
疑難雜癥21:路由器和TCP的互動
雖然有了5.2.4節(jié)介紹的主動的擁塞檢測,那么路由器能不能做點什么幫助檢測擁塞呢?這種對路由器的擴展是必要的,要知道,每天有無數(shù)的TCP要通過路由器,雖然路由器不管TCP協(xié)議的任何事(當然排除連接跟蹤之類的,這里所說的是標準的IP路由器),但是它卻能以一種很簡單的方式告訴TCP的兩端IP網(wǎng)絡發(fā)生了擁堵,這種方式就是當路由器檢測到自己發(fā)生輕微擁堵的時候隨機的丟包,隨機丟包而不是連續(xù)丟包對于TCP而言是有重大意義的,隨機丟包會使TCP發(fā)現(xiàn)丟棄了個別的分段而后續(xù)的分段仍然會到達接收端,這樣TCP發(fā)送端就會接收到3個冗余ACK,然后進入快速重傳/快速恢復而不是慢啟動。
這就是路由器能幫TCP做的事。
其它
疑難雜癥22:如何學習TCP
很多人發(fā)帖問TCP相關的內(nèi)容,接下來稀里嘩啦的就是讓看《TCP/IP詳解》和《Unix網(wǎng)絡編程》里面的特定章節(jié),我覺得這種回答很不負責任。因為我并不認為這兩本書有多大的幫助,寫得確實很不錯,然而可以看出Richard Stevens是一個實用主義者,他喜歡用實例來解釋一切,《詳解》通篇都是用tcpdump的輸出來講述的,這種方式只是適合于已經(jīng)對TCP很理解的人,然而大多數(shù)的人是看不明白的。
如果想從設計的角度來說,這兩本書都很爛。我覺得應該先看點入門的,比如Wiki之類的,然后看RFC文檔,793,896,1122等),這樣你就明白 TCP為何這么設計了,而這些你永遠都不能在Richard Stevens的書中得到。最后,如果你想,那么就看一點Richard Stevens的書,最重要的還是寫點代碼或者敲點命令,然后抓包自己去分析。
疑難雜癥23:Linux,Windows和網(wǎng)絡編程
我覺得在Linux上寫點TCP的代碼是很不錯的,如果有BSD那就更好了。不推薦用Winsock學習TCP。雖然微軟聲稱自己的API都是為了讓事情更簡單,但實際上事情卻更復雜了,如果你用Winsock學習,你就要花大量的時候去掌握一些和網(wǎng)絡編程無關但是windows平臺上卻少不了的東西
1.總結(jié)
TCP 協(xié)議是一個端到端的協(xié)議,雖然話說它是一個帶流量控制,擁塞控制的協(xié)議,然而正是因為這些所謂的控制才導致了TCP變得復雜。同時這些特性是互相雜糅的,流量控制帶來了很多問題,解決這些問題的方案最終又帶來了新的問題,這些問題在解決的時候都只考慮了端到端的意義,但實際上TCP需要盡力而為的IP提供的網(wǎng)絡,因此擁塞成了最終的結(jié)癥,擁塞控制算法的改進也成了一個單獨的領域。
在學習TCP的過程中,切忌一鍋粥一盤棋的方式,一定要分清楚每一個算法到底是解決什么問題的,每一個問題和其他問題到底有什么關聯(lián),這些問題的解決方案之間有什么關聯(lián),另外TCP的發(fā)展歷史也了解一下,這些都搞明白了,TCP協(xié)議就徹底被你掌控了。接下來你就可以學習Socket API了,然后高效的TCP程序出自你手!