1. 程式人生 > >TCP協議學習總結(中)

TCP協議學習總結(中)

性能 bsp 復雜 完美 設置 通告 數據回顯 實時推送 實時性

很多人都說TCP協議是一個十分復雜的協議,在學習當中,我對協議每一個問題都分解學習後,每一個分解我都能體會和理解它的要點,並不難理解。但我把這些拆分的細節合並後,確認感覺這樣一個協議相對“臃腫”但又好像不得不這樣做的感覺。也寫過那麽多年代碼,我也十分理解這種“分布”和“一致”的協調,就好像CAP理論一樣,更關鍵的是許多的CAP選擇都是依賴於TCP這樣可靠的協議之上,可想而知它“可靠性”的重中之中,我也看到了根基紮實穩重的重要性。當然技術還在不斷進步,協議的完善和優化從沒有停止,無論如何,學習還得繼續。

TCP的交互數據流

1、交互式輸入

有些場景比如聊天,對信息交互的時效性非常敏感,又還比如遠程操作,我們在本地點擊一下鼠標的遠程操作,需要被操作設備非常及時的響應,這樣一些場景就是交互式輸入。

技術分享圖片

交互式輸入是場景需要之一,它是犧牲了網絡利用率而滿足了時間的一種選擇,試想一下如果客戶端每一個小小的動作(如點擊ENTER鍵產生1個字節)就要帶著20個字節的TCP頭部以及20個自己的IP頭部,一共41個字節在網絡上跑,如果每種交互都是這樣的話,網絡的利用率大大降低,所以這種情況更多適用於局域網內。而且在這種交互式交互時還可以看到TCP頭部的PSH標識被設置了,它的意思是趕緊把數據交給應用程序,而不是先放緩沖區。

客戶端的實時推送請求以及服務端的及時響應確實能保證了“實時交互”的效果,但這裏會隱藏了一個客戶端對服務端數據回顯確認報文(ACK)的一個延遲發送。我們都知道TCP交互是一個可靠的協議,所以對數據的接收和確認是必然的要做的事情,我覺得既然客戶端已經得到了服務端的及時響應,對服務端數據的確認響應已經不需要那麽及時了。好好地在這一個點上的優化可以節省了網絡的不必要浪費。

技術分享圖片

通常TCP在接收到數據時並不會立刻發送ACK,相反,它推遲發送,以便將ACK與需要沿該方向發送的數據一起發送(有時這種現象為數據捎帶ACK)。絕大多數實現采用的時延為200ms,也就是說,TCP將以最大200ms的時延等待是否有數據一起發送,這樣做就節省了不必要的網絡開銷,從這一點看,TCP協議也是想絕了,畢竟資源珍貴,容不得半丁點的浪費。

2、Nagle算法

雖然交互式輸入更多適用在局域網,但廣域網就不能用這是不可能的,實時交互式這種場景會產生許多的(微)小分組,例如41字節中的真正數據才1字節。這確實會增加廣域網擁塞的可能性。但是“道高一尺,魔高一丈”,一種簡單和好的方法應運而生,那就是Nagle算法。該算法要求一個TCP連接上最多只有一個未被確認的未完成的小分組,在該分組的確認到達之前不能發送其它的小分組,相反,TCP收集這些少量的分組,並在確認到來時以一個分組的方式發出去。

該算法的優越之處在於它是自適應的:確認到達得越快,數據也就發送得越快。而在希望減少微小分組數目的低速廣域網上則會發送更少的分組。

技術分享圖片

從上圖可以看到,因為Nagle算法的生效,所以在“按鍵”輸出後沒有等到服務端響應確認前的後續4次數據輸入都無法發送,而是等到服務端確認ACK到達後一起打包發送。有一點需要註意的是,這裏的輸入假設都是小分組輸入,並沒有很大的數據輸入(不超過MSS)。Nagle算法算是在廣域網上做了一個這種的選擇,對數據交互實時性的影響不會太大,而又對互聯網做了一層較好的保護,讓交互的效果自適應網絡的狀態。

TCP的成塊數據流

1、滑動窗口

交互式輸入我覺得從綜合場景來看,相對少數。大部分情況下,大塊的數據流交互才是“王道”,這裏並不是說我們平時的應用交互就不需要實時,只不過這個“實時”是相對的,在數據量大的情況下如何才是最佳的交互體驗,具體問題具體分析才是王道。在TCP頭部中我們知道有一個叫做“16位窗口大小”的屬性,這個窗口在上一節也介紹過相當於數據接收的“緩存區”,數據交互的雙方(無論主動還是被動)都會維護著自己的一個窗口,應用程序沒有消費數據之前都是停留在這個緩沖區中。所以,窗口可以看做TCP交互限流的一個關鍵所在,如果窗口爆滿的一方是無法接收數據了,發送方也只能暫停發送,等待對方窗口的空閑才繼續發送。畢竟雙方應用程序處理數據的速度不一或者網絡網速的客觀影響,很難確認完美的狀態,所以很需要一個像窗口一樣的概念去維護雙方之間的一個傳送速度。

技術分享圖片

通過上圖可以看到,這次大塊數據交互不像“交互式輸入”那樣小分組發送,而是每次發送都會充滿整個報文段允許的最大值(MSS)從而達到更加的效率,當然這已經是另一次場景了,而這個場景才是我們平常使用到的場景,例如你看個新聞,刷個微博等。從圖中也可以看得到窗口的真實作用,服務端一開始就告訴客戶端它的窗口大小為4096,客戶端要發送的數據遠遠大於4096(例如一張圖片可以就不止了),客戶端可以連續以最大報文段的容量連續發送,直到服務端的窗口(緩沖區)被沾滿時,才會停止發送,等待服務端的窗口大小變更通知,再繼續發送。其實這裏的成塊數據傳送隱藏了許多的規則細節,例如發送的數據不夠MSS怎麽辦,需要等麽?又或者服務端的窗口可以騰出了1個字節的空間也要告訴客戶端的話就會引發“糊塗窗口綜合癥”的問題。這些細節後續會慢慢介紹,這裏更多先總結窗口的基本作用。

技術分享圖片

窗口(緩沖區)的總大小是固定的,只不過因為窗口的“空閑”是隨著接收方的確認而變動,從而讓窗口看起來是動態變化的,這個動態(左右收縮)其實是站在雙方的視覺看窗口的空閑狀態而言,雙方都會計算和維護當前窗口的大小, 例如發送方會計算它到底還有多少數據可以發送,而接收方會計算它到底還有多少窗口空間可以接收數據:

1)、稱窗口左邊沿向右邊沿靠近為窗口靠攏,這種現象發生在數據被發送和確實時;

2)、當窗口右邊沿向右移動時將允許發送更多的數據,我們稱之為窗口張開。這種現象發生在另一端的接收進程讀取已經確認的數據並釋放了TCP的接收緩存時;

3)、當右邊沿向左移動時,我們稱之為窗口收縮。RPC強烈建議不要使用這種方式。

如果左邊沿到達右邊沿,則稱其為一個零窗口,此時發送方不能夠發送任何數據。就像“TCP窗口樣例”顯示的那樣。不同系統默認的窗口大小不一樣,例如有些是2048字節,有些是4096、8192或更大。但重要是“插口API允許進程設置發送和接收緩存的大小,接收緩存的大小是該連接上所能通告的最大窗口大小,有一些應用程序通過修改插口緩存大小來增加性能”

2、慢啟動

“接收窗口”並非萬能,雖然可以通過增加緩存大小提高性能,但影響性能的遠不止“接收窗口”的大小,還有網速、路由器等。迄今為止,發送方一開始便向網絡發送多個報文段,直至達到接收方通告窗口大小為止。當發送方和接收方處於同一個局域網時這種方式是可以的,但是如果在發送方和接收方之間存在多個路由器時,就有可能出現一些問題。一些中間路由器必須緩存分組,並有可能耗盡存儲器的空間。

現在TCP需要支持一種被稱為“慢啟動(slow start)”的算法。該算法通過觀察到新分組進入網絡的速率應該與另一端返回確認的速率相同而進行工作。慢啟動為發送方的TCP增加了另一個窗口叫“擁塞窗口(congestion window)”,記為cwnd。擁塞窗口是發送方使用的流量控制,而通告窗口則是接收方使用的流量控制。

技術分享圖片

擁塞窗口被初始化為1個報文段(即另一端通告的報文段大小),收到一個ACK後,擁塞窗口從1個報文段增加為2,即可發送兩個報文段,當繼續收到這兩個報文段的ACK時,擁塞窗口就增加為4,這是一種指數增加關系。發送方去擁塞窗口與通告窗口中最小值作為發送上限。其實這宗一種漸進循環的策略,當吞吐量在某些點上達到了互聯網的容量時,於是中間路由器開始丟棄分組,而通知發送方它的擁塞窗口開得過大了。這裏又隱藏了許多的實現細節,如丟失重傳問題以及如何避免一下子又從慢啟動起步這種慢效率的傳輸過程。這些後續會學習到。這裏更多先對慢啟動算法的一個初步認識。

窗口的大小應該設置為多大呢?也就是我們的TCP緩存應該設置多大呢?按理論計算,這個應該跟“帶寬時延乘積”有關,具體公式如下:

capacity(bit)=bandwidth(b/s)* round-trip time(s)

這個值依賴於網絡速度和兩端的RTT,可以有很大的變動。例如一條穿越美國(RTT約為60ms)的T1的電話線路(1544000b/s)的帶寬延遲乘積為11580字節。這是沒有問題的,但對於一條穿越美國的T3電話線路(45000000b/s)的帶寬時延乘積則為337500字節,這個值遠遠超過了最大所允許的TCP通告窗口大小(16字節窗口大小=65535字節)。別忘記了TCP頭部還有一各選項可以補充,後續會介紹如果通過選項解決這種超大型窗口問題。“帶寬時延乘積”是一個理論值,就是我們能往發送到到接收端之間的網絡最多塞滿多少數據。

學習總結

本此總結主要是學習了基於TCP協議交互的一些流程與細節問題,如“交互式輸入”以及“成塊數交互”的場景介紹,在“成塊式傳送”中是如何利用“通告窗口”作為接收方的限流以及發送方如何利用“擁塞窗口”進行限流並借助“慢啟動”的方式漸進循環的摸索互聯網的最大極限。

TCP協議學習總結(中)