第二十章: TCP的成塊數(shù)據(jù)流

20.1 引言

在第15章我們看到TFTP使用了停止等待協(xié)議。數(shù)據(jù)發(fā)送方在發(fā)送下一個數(shù)據(jù)塊之前需要等待接收對已發(fā)送數(shù)據(jù)的確認(rèn)。本章我們將介紹TCP所使用的被稱為滑動窗口協(xié)議的另一種形式的流量控制方法。該協(xié)議允許發(fā)送方在停止并等待確認(rèn)前可以連續(xù)發(fā)送多個分組。由于發(fā)送方不必每發(fā)一個分組就停下來等待確認(rèn),因此該協(xié)議可以加速數(shù)據(jù)的傳輸。

我們還將介紹TCP的PUSH標(biāo)志,該標(biāo)志在前面的許多例子中都出現(xiàn)過。此外,我們還要介紹慢啟動,TCP使用該技術(shù)在一個連接上建立數(shù)據(jù)流,最后介紹成塊數(shù)據(jù)流的吞吐量。

20.2 正常數(shù)據(jù)流

我們以從主機(jī)svr4單向傳輸8192個字節(jié)到主機(jī)bsdi開始。在bsdi上運行sock程序作為服務(wù)器:

bsdi % sock -i -s 7777

其中,標(biāo)志-i和-s指示程序作為一個“吸收(sink)”服務(wù)器運行(從網(wǎng)絡(luò)上讀取并丟棄數(shù)據(jù)),服務(wù)器端口指明為7777。相應(yīng)的客戶程序運行為:

svr4 % sock -i -n8 bsdi 7777

該命令指示客戶向網(wǎng)絡(luò)發(fā)送8個1024字節(jié)的數(shù)據(jù)。圖20-1顯示了這個過程的時間系列。我們在輸出的前3個報文段中顯示了每一端MSS的值。

發(fā)送方首先傳送3個數(shù)據(jù)報文段(4~6)。下一個報文段(7)僅確認(rèn)了前兩個數(shù)據(jù)報文段,這可以從其確認(rèn)序號為2048而不是3073看出來。

報文段7的ACK的序號之所以是2048而不是3073是由以下原因造成的:當(dāng)一個分組到達(dá)時,它首先被設(shè)備中斷例程進(jìn)行處理,然后放置到IP的輸入隊列中。三個報文段4、5和6依次到達(dá)并按接收順序放到IP的輸入隊列。IP將按同樣順序?qū)⑺鼈兘唤oTCP。當(dāng)TCP處理報文段4時,該連接被標(biāo)記為產(chǎn)生一個經(jīng)受時延的確認(rèn)。TCP處理下一報文段(5),由于TCP現(xiàn)在有兩個未完成的報文段需要確認(rèn),因此產(chǎn)生一個序號為2048的ACK(報文段7),并清除該連接產(chǎn)生經(jīng)受時延的確認(rèn)標(biāo)志。TCP處理下一個報文段(6),而連接又被標(biāo)志為產(chǎn)生一個經(jīng)受時延的確認(rèn)。在報文段9到來之前,由于時延定時器溢出,因此產(chǎn)生一個序號為3073的ACK(報文段8)。報文段8中的窗口大小為3072,表明在TCP的接收緩存中還有1024個字節(jié)的數(shù)據(jù)等待被應(yīng)用程序讀取。

報文段11~16說明了通常使用的“隔一個報文段確認(rèn)”的策略。報文段11、12和13到達(dá)并被放入IP的接收隊列。當(dāng)報文段11被處理時,連接被標(biāo)記為產(chǎn)生一個經(jīng)受時延的確認(rèn)。當(dāng)報文段12被處理時,它們的ACK(報文段14)被產(chǎn)生且連接的經(jīng)受時延的確認(rèn)標(biāo)志被清除。報文段13使得連接再次被標(biāo)記為產(chǎn)生經(jīng)受時延。但在時延定時器溢出之前,報文段15處理完畢,因此該確認(rèn)立刻被發(fā)送。


圖20-1 從svr4傳輸8192個字節(jié)到bsdi

注意到報文段7、14和16中的ACK確認(rèn)了兩個收到的報文段是很重要的。使用TCP的滑動窗口協(xié)議時,接收方不必確認(rèn)每一個收到的分組。在TCP中,ACK是累積的—它們表示接收方已經(jīng)正確收到了一直到確認(rèn)序號減1的所有字節(jié)。在本例中,三個確認(rèn)的數(shù)據(jù)為2048字節(jié)而兩個確認(rèn)的數(shù)據(jù)為1024字節(jié)(忽略了連接建立和終止中的確認(rèn))。

用tcpdump看到的是TCP的動態(tài)活動情況。我們在線路上看到的分組順序依賴于許多無法控制的因素:發(fā)送方TCP的實現(xiàn)、接收方TCP的實現(xiàn)、接收進(jìn)程讀取數(shù)據(jù)(依賴于操作系統(tǒng)的調(diào)度)和網(wǎng)絡(luò)的動態(tài)性(如以太網(wǎng)的沖突和退避等)。對這兩個TCP而言,沒有一種單一的、正確的方法來交換給定數(shù)量的數(shù)據(jù)。

為顯示情況可能怎樣變化,圖20-2顯示了在同樣兩個主機(jī)之間交換同樣數(shù)據(jù)時的另一個時間系列,它們是在圖20-1所示的幾分鐘之后截獲的。

一些情況發(fā)生了變化。這一次接收方?jīng)]有發(fā)送一個序號為3073的ACK,而是等待并發(fā)送序號為4097的ACK。接收方僅發(fā)送了4個ACK(報文段7、10、12和15):三個確認(rèn)了2 0 4 8字節(jié),另一個確認(rèn)了1024字節(jié)。最后1024字節(jié)數(shù)據(jù)的ACK出現(xiàn)在報文段17中,它與FIN的ACK一道發(fā)送(比較該圖中的報文段17與圖20-1中的報文段16和18)。


圖20-2 從svr4到bsdi的另外8192字節(jié)數(shù)據(jù)的傳輸過程

快的發(fā)送方和慢的接收方

圖20-3顯示了另外一個時間系列。這次是從一個快的發(fā)送方(一個Sparc工作站)到一個慢的接收方(配有慢速以太網(wǎng)卡的80386機(jī)器)。它的動態(tài)活動情況又有所不同。

發(fā)送方發(fā)送4個背靠背(back-to-back)的數(shù)據(jù)報文段去填充接收方的窗口,然后停下來等待一個ACK。接收方發(fā)送ACK(報文段8),但通告其窗口大小為0,這說明接收方已收到所有數(shù)據(jù),但這些數(shù)據(jù)都在接收方的TCP緩沖區(qū),因為應(yīng)用程序還沒有機(jī)會讀取這些數(shù)據(jù)。另一個ACK(稱為窗口更新)在17.4ms后發(fā)送,表明接收方現(xiàn)在可以接收另外的4096個字節(jié)的數(shù)據(jù)。雖然這看起來像一個ACK,但由于它并不確認(rèn)任何新數(shù)據(jù),只是用來增加窗口的右邊沿,因此被稱為窗口更新。

發(fā)送方發(fā)送最后4個報文段(10~13),再次填充了接收方的窗口。注意到報文段13中包括兩個比特標(biāo)志:PUSH和FIN。隨后從接收方傳來另外兩個ACK,它們確認(rèn)了最后的4096字節(jié)的數(shù)據(jù)(從4097到8192字節(jié))和FIN(標(biāo)號為8192)。

圖20-3 從一個快發(fā)送方發(fā)送8192字節(jié)的數(shù)據(jù)到一個慢接收方

20.3 滑動窗口

圖20-4用可視化的方法顯示了我們在前一節(jié)觀察到的滑動窗口協(xié)議。

圖20-4 TCP滑動窗口的可視化表示

在這個圖中,我們將字節(jié)從1至11進(jìn)行標(biāo)號。接收方通告的窗口稱為提出的窗口(offered window),它覆蓋了從第4字節(jié)到第9字節(jié)的區(qū)域,表明接收方已經(jīng)確認(rèn)了包括第3字節(jié)在內(nèi)的數(shù)據(jù),且通告窗口大小為6?;仡櫟?7章,我們知道窗口大小是與確認(rèn)序號相對應(yīng)的。發(fā)送方計算它的可用窗口,該窗口表明多少數(shù)據(jù)可以立即被發(fā)送。

當(dāng)接收方確認(rèn)數(shù)據(jù)后,這個滑動窗口不時地向右移動。窗口兩個邊沿的相對運動增加或減少了窗口的大小。我們使用三個術(shù)語來描述窗口左右邊沿的運動:

1:稱窗口左邊沿向右邊沿靠近為窗口合攏。這種現(xiàn)象發(fā)生在數(shù)據(jù)被發(fā)送和確認(rèn)時。

2:當(dāng)窗口右邊沿向右移動時將允許發(fā)送更多的數(shù)據(jù),我們稱之為窗口張開。這種現(xiàn)象發(fā)生在另一端的接收進(jìn)程讀取已經(jīng)確認(rèn)的數(shù)據(jù)并釋放了TCP的接收緩存時。

3:當(dāng)右邊沿向左移動時,我們稱之為窗口收縮。Host Requirements RFC強(qiáng)烈建議不要使用這種方式。但TCP必須能夠在某一端產(chǎn)生這種情況時進(jìn)行處理。第22.3節(jié)給出了這樣的一個例子,一端希望向左移動右邊沿來收縮窗口,但沒能夠這樣做。

圖20-5表示了這三種情況。因為窗口的左邊沿受另一端發(fā)送的確認(rèn)序號的控制,因此不可能向左邊移動。如果接收到一個指示窗口左邊沿向左移動的ACK,則它被認(rèn)為是一個重復(fù)ACK,圖20-5窗口邊沿的移動并被丟棄。

圖20-5 窗口邊沿的移動

如果左邊沿到達(dá)右邊沿,則稱其為一個零窗口,此時發(fā)送方不能夠發(fā)送任何數(shù)據(jù)。

一個例子

圖20-6顯示了在圖20-1所示的數(shù)據(jù)傳輸過程中滑動窗口協(xié)議的動態(tài)性。


圖20-6 圖20-1的滑動窗口協(xié)議

以該圖為例可以總結(jié)如下幾點:

1:發(fā)送方不必發(fā)送一個全窗口大小的數(shù)據(jù)。

2:來自接收方的一個報文段確認(rèn)數(shù)據(jù)并把窗口向右邊滑動。這是因為窗口的大小是相對于確認(rèn)序號的。

3:正如從報文段7到報文段8中變化的那樣,窗口的大小可以減小,但是窗口的右邊沿卻不能夠向左移動。

4:接收方在發(fā)送一個ACK前不必等待窗口被填滿。在前面我們看到許多實現(xiàn)每收到兩個報文段就會發(fā)送一個ACK。

下面我們可以看到更多的滑動窗口協(xié)議動態(tài)變化的例子。

20.4 窗口大小

由接收方提供的窗口的大小通??梢杂山邮者M(jìn)程控制,這將影響TCP的性能。

4.2BSD默認(rèn)設(shè)置發(fā)送和接受緩沖區(qū)的大小為2048個字節(jié)。在4.3BSD中雙方被增加為4096個字節(jié)。正如我們在本書中迄今為止所看到的例子一樣,SunOS 4.1.3、BSD/386和SVR4仍然使用4096字節(jié)的默認(rèn)大小。其他的系統(tǒng),如Solaris 2.2、4.4BSD和AIX3.2則使用更大的默認(rèn)緩存大小,如8192或16384等。


插口API允許進(jìn)程設(shè)置發(fā)送和接收緩存的大小。接收緩存的大小是該連接上所能夠通告的最大窗口大小。有一些應(yīng)用程序通過修改插口緩存大小來增加性能。

[Mogul 1993]顯示了在改變發(fā)送和接收緩存大小(在單向數(shù)據(jù)流的應(yīng)用中,如文件傳輸,只需改變發(fā)送方的發(fā)送緩存和接收方的接收緩存大?。┑那闆r下,位于以太網(wǎng)上的兩個工作站之間進(jìn)行文件傳輸時的一些結(jié)果。它表明對以太網(wǎng)而言,默認(rèn)的4096字節(jié)并不是最理想的大小,將兩個緩存增加到16384個字節(jié)可以增加約40%左右的吞吐量。在[Papadopoulos和Parulkar 1993]中也有相似的結(jié)果。

在20.7節(jié)中,我們將看到在給定通信媒體帶寬和兩端往返時間的情況下,如何計算最小的緩存大小。

一個例子

可以使用sock程序來控制這些緩存的大小。我們以如下方式調(diào)用服務(wù)器程序:

bsdi % sock -i -s -R6144 5555

該命令設(shè)置接收緩存為6144個字節(jié)(-R選項)。接著我們在主機(jī)sun上啟動客戶程序并使之發(fā)送8192個字節(jié)的數(shù)據(jù):

sun % sock -i -n1 -w8192 bsdi 5555

圖20-7顯示了結(jié)果。

首先注意到的是在報文段2中提供的窗口大小為6144字節(jié)。由于這是一個較大的窗口,因此客戶立即連續(xù)發(fā)送了6個報文段(4~9),然后停止。報文段10確認(rèn)了所有的數(shù)據(jù)(從第1到6144字節(jié)),但提供的窗口大小卻為2048,這很可能是接收程序沒有機(jī)會讀取多于2048字節(jié)的數(shù)據(jù)。報文段11和12完成了客戶的數(shù)據(jù)傳輸,且最后一個報文段帶有FIN標(biāo)志。

報文段13包含與報文段10相同的確認(rèn)序號,但通告了一個更大的窗口大小。報文段14確認(rèn)了最后的2048字節(jié)的數(shù)據(jù)和FIN,報文段15和16僅用于通告一個更大的窗口大小。報文段17和18完成通常的關(guān)閉過程。

圖20-7 接收方提供一個6144字節(jié)的接收窗口的情況下的數(shù)據(jù)傳輸

20.5 PUSH標(biāo)志

在每一個TCP例子中,我們都看到了PUSH標(biāo)志,但一直沒有介紹它的用途。發(fā)送方使用該標(biāo)志通知接收方將所收到的數(shù)據(jù)全部提交給接收進(jìn)程。這里的數(shù)據(jù)包括與PUSH一起傳送的數(shù)據(jù)以及接收方TCP已經(jīng)為接收進(jìn)程收到的其他數(shù)據(jù)。

在最初的TCP規(guī)范中,一般假定編程接口允許發(fā)送進(jìn)程告訴它的TCP何時設(shè)置PUSH標(biāo)志。例如,在一個交互程序中,當(dāng)客戶發(fā)送一個命令給服務(wù)器時,它設(shè)置PUSH標(biāo)志并停下來等待服務(wù)器的響應(yīng)(在習(xí)題19.1中我們假定當(dāng)發(fā)送12字節(jié)的請求時客戶設(shè)置PUSH標(biāo)志)。通過允許客戶應(yīng)用程序通知其TCP設(shè)置PUSH標(biāo)志,客戶進(jìn)程通知TCP在向服務(wù)器發(fā)送一個報文段時不要因等待額外數(shù)據(jù)而使已提交數(shù)據(jù)在緩存中滯留。類似地,當(dāng)服務(wù)器的TCP接收到一個設(shè)置了PUSH標(biāo)志的報文段時,它需要立即將這些數(shù)據(jù)遞交給服務(wù)器進(jìn)程而不能等待判斷是否還會有額外的數(shù)據(jù)到達(dá)。

然而,目前大多數(shù)的API沒有向應(yīng)用程序提供通知其TCP設(shè)置PUSH標(biāo)志的方法。的確,許多實現(xiàn)程序認(rèn)為PUSH標(biāo)志已經(jīng)過時,一個好的TCP實現(xiàn)能夠自行決定何時設(shè)置這個標(biāo)志。

如果待發(fā)送數(shù)據(jù)將清空發(fā)送緩存,則大多數(shù)的源于伯克利的實現(xiàn)能夠自動設(shè)置PUSH標(biāo)志。這意味著我們能夠觀察到每個應(yīng)用程序?qū)懙臄?shù)據(jù)均被設(shè)置了PUSH標(biāo)志,因為數(shù)據(jù)在寫的時候就立即被發(fā)送。

代碼中的注釋表明該算法對那些只有在緩存被填滿或收到一個PUSH標(biāo)志時才向應(yīng)用程序提交數(shù)據(jù)的TCP實現(xiàn)有效。

使用插口API通知TCP設(shè)置正在接收數(shù)據(jù)的PUSH標(biāo)志或得到該數(shù)據(jù)是否被設(shè)置PUSH標(biāo)志的信息是不可能的。

由于源于伯克利的實現(xiàn)一般從不將接收到的數(shù)據(jù)推遲交付給應(yīng)用程序,因此它們忽略所接收的PUSH標(biāo)志。

舉例

在圖20-1中我們觀察到所有8個數(shù)據(jù)報文段(46、9、1113和15)的PUSH標(biāo)志均被置1,這是因為客戶進(jìn)行了8次1024字節(jié)數(shù)據(jù)的寫操作,并且每次寫操作均清空了發(fā)送緩存。

再次觀察圖20-7,我們預(yù)計報文段12中的PUSH標(biāo)志被置1,因為它是最后一個報文段。為什么發(fā)送方知道有更多的數(shù)據(jù)需要發(fā)送還設(shè)置報文段7中的PUSH標(biāo)志呢?這是因為雖然我們指定寫的是8192個字節(jié)的數(shù)據(jù),但發(fā)送方的發(fā)送緩存卻是4096個字節(jié)。

值得注意的另外一點是在圖20-7中的第14、15和16這三個連續(xù)的確認(rèn)報文段。在圖20-3中我們也觀察到了兩個連續(xù)的ACK,但那是因為接收方已經(jīng)通告其窗口為0(使發(fā)送方停止)。當(dāng)窗口張開時,需要發(fā)送另一個窗口非0的ACK來使發(fā)送方重新啟動??墒牵趫D20-7中,窗口的大小從來沒有達(dá)到過0。然而,當(dāng)窗口大小增加了2048個字節(jié)的時候,另一個ACK(報文段15和16)被發(fā)送以通知對方窗口被更新(在報文段15和16中,這兩個窗口更新是不需要的,因為已經(jīng)收到了對方的FIN,表明它不會再發(fā)送任何數(shù)據(jù))。許多TCP實現(xiàn)在窗口大小增加了兩個最大報文段長度(本例中為2048字節(jié),因為MSS為1024字節(jié))或者最大可能窗口的50%(本例中為2048字節(jié),因為最大窗口大小為4096字節(jié))時發(fā)送這個窗口更新。在第22.3節(jié)詳細(xì)考察糊涂窗口綜合癥的時候,我們還會看到這種現(xiàn)象。

作為PUSH標(biāo)志的另一個例子,再次回到圖20-3。我們之所以看到前4個報文段(4~7)的標(biāo)志被設(shè)置,是因為它們每一個均使TCP產(chǎn)生了一個報文段并提交給IP層。但是隨后,TCP停下來等待一個確認(rèn)來移動4096字節(jié)的窗口。在此期間,TCP又得到了應(yīng)用程序的最后4096個字節(jié)的數(shù)據(jù)。當(dāng)窗口張開時(報文段9),發(fā)送方TCP知道它有4個可立即發(fā)送的報文段,因此它只設(shè)置了最后一個報文段(13)的PUSH標(biāo)志。

20.6 慢啟動

迄今為止,在本章所有的例子中,發(fā)送方一開始便向網(wǎng)絡(luò)發(fā)送多個報文段,直至達(dá)到接收方通告的窗口大小為止。當(dāng)發(fā)送方和接收方處于同一個局域網(wǎng)時,這種方式是可以的。但是如果在發(fā)送方和接收方之間存在多個路由器和速率較慢的鏈路時,就有可能出現(xiàn)一些問題。一些中間路由器必須緩存分組,并有可能耗盡存儲器的空間。[Jacobson 1988]證明了這種連接方式是如何嚴(yán)重降低了TCP連接的吞吐量的。

現(xiàn)在,TCP需要支持一種被稱為“慢啟動(slow start)”的算法。該算法通過觀察到新分組進(jìn)入網(wǎng)絡(luò)的速率應(yīng)該與另一端返回確認(rèn)的速率相同而進(jìn)行工作。

慢啟動為發(fā)送方的TCP增加了另一個窗口:擁塞窗口(congestion window),記為cwnd。當(dāng)與另一個網(wǎng)絡(luò)的主機(jī)建立TCP連接時,擁塞窗口被初始化為1個報文段(即另一端通告的報文段大?。?。每收到一個ACK,擁塞窗口就增加一個報文段(cwnd以字節(jié)為單位,但是慢啟動以報文段大小為單位進(jìn)行增加)。發(fā)送方取擁塞窗口與通告窗口中的最小值作為發(fā)送上限。擁塞窗口是發(fā)送方使用的流量控制,而通告窗口則是接收方使用的流量控制。

發(fā)送方開始時發(fā)送一個報文段,然后等待ACK。當(dāng)收到該ACK時,擁塞窗口從1增加為2,即可以發(fā)送兩個報文段。當(dāng)收到這兩個報文段的ACK時,擁塞窗口就增加為4。這是一種指數(shù)增加的關(guān)系。

在某些點上可能達(dá)到了互聯(lián)網(wǎng)的容量,于是中間路由器開始丟棄分組。這就通知發(fā)送方它的擁塞窗口開得過大。當(dāng)我們在下一章討論TCP的超時和重傳機(jī)制時,將會看到它們是怎樣對擁塞窗口起作用的?,F(xiàn)在,我們來觀察一個實際中的慢啟動。

一個例子

圖20-8表示的是將從主機(jī)sun發(fā)送到主機(jī)vangogh.cs.berkeley.edu的數(shù)據(jù)。這些數(shù)據(jù)將通過一個慢的SLIP鏈路,該鏈路是TCP連接上的瓶頸(我們已經(jīng)在時間系列上去掉了連接建立的過程)。


圖20-8 慢啟動的例子

我們觀察到發(fā)送方發(fā)送一個長度為512字節(jié)的報文段,然后等待ACK。該ACK在716 ms后收到。這個時間是一個往返時間的指示。于是擁塞窗口增加了2個報文段,且又發(fā)送了兩個報文段。當(dāng)收到報文段5的ACK后,擁塞窗口增加為3。此時盡管可發(fā)送多達(dá)3個報文段,可是在下一個ACK收到之前,只發(fā)送了2個報文段。

在21.6節(jié)中我們將再次討論慢啟動,并介紹怎樣采用另一種被稱為“擁塞避免”的技術(shù)來作為通常的實現(xiàn)。

20.7 成塊數(shù)據(jù)的吞吐量

讓我們看一看窗口大小、窗口流量控制以及慢啟動對傳輸成塊數(shù)據(jù)的TCP連接的吞吐量的相互作用。

圖20-9顯示了左邊的發(fā)送方和右邊的接收方之間的一個TCP連接上的時間系列,共顯示了16個時間單元。為簡單起見,本圖只顯示離散的時間單元。每個粗箭頭線的上半部分顯示的是從左到右的攜帶數(shù)據(jù)的報文段,標(biāo)記為1,2,3,等等。在粗線箭頭下面表示的是反向傳輸?shù)腁CK。我們把ACK用細(xì)箭頭線表示,并標(biāo)注了被確認(rèn)的報文段號。

圖20-9 時間0~15的成塊數(shù)據(jù)吞吐量舉例

在時間0,發(fā)送方發(fā)送了一個報文段。由于發(fā)送方處于慢啟動中(其擁塞窗口為1個報文段),因此在繼續(xù)發(fā)送以前它必須等待該數(shù)據(jù)段的確認(rèn)。

在時間1,2和3,報文段從左向右移動一個時間單元。在時間4接收方讀取這個報文段并產(chǎn)生確認(rèn)。經(jīng)過時間5、6和7,ACK移動到左邊的發(fā)送方。我們有了一個8個時間單元的往返時間RTT(Round-Trip Time)。

我們有意把ACK報文段畫得比數(shù)據(jù)報文段小,這是因為它通常只有一個IP首部和一個TCP首部。這里顯示僅僅是一個單向的數(shù)據(jù)流動,并且假定ACK的移動速率與數(shù)據(jù)報文段的移動速率相等。實際上并不總是這樣。

通常發(fā)送一個分組的時間取決于兩個因素:傳播時延(由光的有限速率、傳輸設(shè)備的等待時間等引起)和一個取決于媒體速率(即媒體每秒可傳輸?shù)谋忍財?shù))的發(fā)送時延。對于一個給定的兩個接點之間的通路,傳播時延一般是固定的,而發(fā)送時延則取決于分組的大小。在速率較慢的情況下發(fā)送時延起主要作用(例如,在習(xí)題7.2中我們甚至沒有考慮傳播時延),而在千兆比特速率下傳播時延則占主要地位(見圖24-6)。

當(dāng)發(fā)送方收到ACK后,在時間8和9發(fā)送兩個報文段(我們標(biāo)記為2和3)。此時它的擁塞窗口為2個報文段。這兩個報文段向右傳送到接收方,在時間12和13接收方產(chǎn)生兩個ACK。這兩個返回到發(fā)送方的ACK之間的間隔與報文段之間的間隔一致,被稱為TCP的自計時(self-clocking)行為。由于接收方只有在數(shù)據(jù)到達(dá)時才產(chǎn)生ACK,因此發(fā)送方接收到的ACK之間的間隔與數(shù)據(jù)到達(dá)接收方的間隔是一致的(然而在實際中,返回路徑上的排隊會改變ACK的到達(dá)率)。

圖20-10表示的是后面16個時間單位。2個ACK的到達(dá)使得擁塞窗口從2個報文段增加為4個,而這4個報文段在時間1619時被發(fā)送。第1個ACK在時間23到達(dá)。4個ACK的到達(dá)使得擁塞窗口從4個報文段增加為8個,并在時間2431發(fā)送8個報文段。

圖20-10 時間16~31的成塊數(shù)據(jù)吞吐量舉例

在時間31及其后續(xù)時間,發(fā)送方和接收方之間的管道(pipe)被填滿。此時不論擁塞窗口和通告窗口是多少,它都不能再容納更多的數(shù)據(jù)。每當(dāng)接收方在某一個時間單位從網(wǎng)絡(luò)上移去一個報文段,發(fā)送方就再發(fā)送一個報文段到網(wǎng)絡(luò)上。但是不管有多少報文段填充了這個管道,返回路徑上總是具有相同數(shù)目的ACK。這就是連接的理想穩(wěn)定狀態(tài)。

20.7.1 帶寬時延乘積

現(xiàn)在來回答窗口應(yīng)該設(shè)置為多大的問題。在我們的例子中,作為最大的吞吐量,發(fā)送方在任何時候有8個已發(fā)送的報文段未被確認(rèn)。接收方的通告窗口必須不小于這個數(shù)目,因為通告窗口限制了發(fā)送方能夠發(fā)送的段的數(shù)目。

可以計算通道的容量為:

capacity (bit) = bandwidth (b/s) × round-trip time (s)
一般稱之為帶寬時延乘積。這個值依賴于網(wǎng)絡(luò)速度和兩端的RT T,可以有很大的變動。例如,一條穿越美國(RT T約為60 ms)的T1的電話線路(1544 000 b/s)的帶寬時延乘積為11 580字節(jié)。對于20.4節(jié)中討論的緩存大小而言,這個結(jié)果是合理的。但是一條穿越美國的T3電話線路(45 000 000 b/s)的帶寬時延乘積則為337 500字節(jié),這個數(shù)值超過了最大所允許的TCP通告窗口的大?。?5535字節(jié))。在24.4節(jié)我們將討論能夠避免當(dāng)前TCP限制的新的TCP窗口大小選項。

T1電話線的1544 000 b/s是原始比特率。由于每193個bit使用1個作為幀同步,因此實際數(shù)據(jù)率為1536 000 b/s。一個T3電話線的原始比特率實際上是44 736 000 b/s,其數(shù)據(jù)率可達(dá)到44 210 000 b/s。在討論中我們使用1.544 Mb/s和45 Mb/s。
不論是帶寬還是時延均會影響發(fā)送方和接收方之間通路的容量。在圖20-11中我們顯示了一個增加了一倍的RT T會使通路容量也增加一倍。

在圖20-11底下的說明部分,通過使用一個較長的RT T,這個管道能夠容納8個報文段而不是4個。

類似地,圖20-12表示了增加一倍的帶寬也可使該管道的容量增加一倍。


在圖20-12的下部,假定網(wǎng)絡(luò)速率已經(jīng)加倍,使得我們能夠只使用上面一半的時間來發(fā)送4個報文段。這樣,該管道的容量再次加倍(假定該圖的上半部分與下半部分中的報文段具有同樣大小,即具有相同的比特數(shù))。

20.7.2 擁塞

當(dāng)數(shù)據(jù)到達(dá)一個大的管道(如一個快速局域網(wǎng))并向一個較小的管道(如一個較慢的廣域網(wǎng))發(fā)送時便會發(fā)生擁塞。當(dāng)多個輸入流到達(dá)一個路由器,而路由器的輸出流小于這些輸入流的總和時也會發(fā)生擁塞。

圖20-13顯示了一個典型的大管道向小管道發(fā)送報文的情況。之所以說它典型,是因為大多數(shù)的主機(jī)都連接在局域網(wǎng)上,并通過一個路由器與速率相對較低的廣域網(wǎng)相連(我們再次假定圖中上半部分的報文段(9~20)都是相同的,而圖中下半部分的ACK也都是相同的)。


圖20-13 從較大管道向較小管道發(fā)送分組引起的擁塞

在該圖中,我們已經(jīng)標(biāo)記路由器R1為“瓶頸”,因為它是擁塞發(fā)生的地方。它從左側(cè)速率較高的局域網(wǎng)接收數(shù)據(jù)并向右側(cè)速率較低的廣域網(wǎng)發(fā)送(通常R1與R3是同樣的路由器,如同R2與R4一樣。但這并不是必需的,有時也會使用不對稱的路徑)。當(dāng)路由器R2將所接收到的分組發(fā)送到右側(cè)的局域網(wǎng)時,這些分組之間維持與其左側(cè)廣域網(wǎng)上同樣的間隔,盡管局域網(wǎng)具有更高的帶寬。類似地,返回的確認(rèn)之間的間隔也與其在路徑中最慢的鏈路上的間隔一致。

在圖20-13中已經(jīng)假定發(fā)送方不使用慢啟動,它按照局域網(wǎng)的帶寬盡可能快地發(fā)送編號為1~20的報文段(假定接收方的通告窗口至少為20個報文段)。正如我們看到的那樣,ACK之間的間隔與在最慢鏈路上的一致。假定瓶頸路由器具有足夠的容納這20個分組的緩存。如果這個不能保證,就會引起路由器丟棄分組。在21.6節(jié)討論避免擁塞時會看到怎樣避免這種情況。

20.8 緊急方式

TCP提供了“緊急方式(urgent mode)”,它使一端可以告訴另一端有些具有某種方式的“緊急數(shù)據(jù)”已經(jīng)放置在普通的數(shù)據(jù)流中。另一端被通知這個緊急數(shù)據(jù)已被放置在普通數(shù)據(jù)流中,由接收方?jīng)Q定如何處理。

可以通過設(shè)置TCP首部(圖17-2)中的兩個字段來發(fā)出這種從一端到另一端的緊急數(shù)據(jù)已經(jīng)被放置在數(shù)據(jù)流中的通知。URG比特被置1,并且一個16bit的緊急指針被置為一個正的偏移量,該偏移量必須與TCP首部中的序號字段相加,以便得出緊急數(shù)據(jù)的最后一個字節(jié)的序號。

仍有許多關(guān)于緊急指針是指向緊急數(shù)據(jù)的最后一個字節(jié)還是指向緊急數(shù)據(jù)最后一個字節(jié)的下一個字節(jié)的爭論。最初的TCP規(guī)范給出了兩種解釋,但Host RequirementsRFC確定指向最后一個字節(jié)是正確的。

然而,問題在于大多數(shù)的實現(xiàn)(包括源自伯克利的實現(xiàn))繼續(xù)使用錯誤的解釋。所有符合Host Requirements RFC的實現(xiàn)都是可兼容的,但很有可能無法與其他大多數(shù)主機(jī)正確通信。

TCP必須通知接收進(jìn)程,何時已接收到一個緊急數(shù)據(jù)指針以及何時某個緊急數(shù)據(jù)指針還不在此連接上,或者緊急指針是否在數(shù)據(jù)流中向前移動。接著接收進(jìn)程可以讀取數(shù)據(jù)流,并必須能夠被告知何時碰到了緊急數(shù)據(jù)指針。只要從接收方當(dāng)前讀取位置到緊急數(shù)據(jù)指針之間有數(shù)據(jù)存在,就認(rèn)為應(yīng)用程序處于“緊急方式”。在緊急指針通過之后,應(yīng)用程序便轉(zhuǎn)回到正常方式。

TCP本身對緊急數(shù)據(jù)知之甚少。沒有辦法指明緊急數(shù)據(jù)從數(shù)據(jù)流的何處開始。TCP通過連接傳送的唯一信息就是緊急方式已經(jīng)開始(TCP首部中的URG比特)和指向緊急數(shù)據(jù)最后一個字節(jié)的指針。其他的事情留給應(yīng)用程序去處理。

不幸的是,許多實現(xiàn)不正確地稱TCP的緊急方式為帶外數(shù)據(jù)(out-of-band data)。如果一個應(yīng)用程序確實需要一個獨立的帶外信道,第二個TCP連接是達(dá)到這個目的的最簡單的方法(許多運輸層確實提供許多人認(rèn)為的那種真正的帶外數(shù)據(jù):使用同一個連接的獨立的邏輯數(shù)據(jù)通道作為正常的數(shù)據(jù)通道。這是TCP所沒有提供的)。

TCP的緊急方式與帶外數(shù)據(jù)之間的混淆,也是因為主要的編程接口(插口API)將TCP的緊急方式映射為稱為帶外數(shù)據(jù)的插口。

緊急方式有什么作用呢?兩個最常見的例子是Telnet和Rlogin。當(dāng)交互用戶鍵入中斷鍵時,我們在第26章將看到使用緊急方式來完成這個功能的例子。另一個例子是FTP,當(dāng)交互用戶放棄一個文件的傳輸時,我們將在第27章看到這樣的一個例子。

Telnet和Rlogin從服務(wù)器到客戶使用緊急方式是因為在這個方向上的數(shù)據(jù)流很可能要被客戶的TCP停止(也即,它通告了一個大小為0的窗口)。但是如果服務(wù)器進(jìn)程進(jìn)入了緊急方式,盡管它不能夠發(fā)送任何數(shù)據(jù),服務(wù)器TCP也會立即發(fā)送緊急指針和URG標(biāo)志。當(dāng)客戶TCP接收到這個通知時就會通知客戶進(jìn)程,于是客戶可以從服務(wù)器讀取其輸入、打開窗口并使數(shù)據(jù)流動。

如果在接收方處理第一個緊急指針之前,發(fā)送方多次進(jìn)入緊急方式會發(fā)生什么情況呢?在數(shù)據(jù)流中的緊急指針會向前移動,而其在接收方的前一個位置將丟失。接收方只有一個緊急指針,每當(dāng)對方有新的值到達(dá)時它將被覆蓋。這意味著如果發(fā)送方進(jìn)入緊急方式時所寫的內(nèi)容對接收方非常重要,那么這些字節(jié)數(shù)據(jù)必須被發(fā)送方用某種方式特別標(biāo)記。我們將看到Telnet通過在數(shù)據(jù)流中加入一個值為255的字節(jié)作為前綴來標(biāo)記它所有的命令。

一個例子

讓我們觀察一下即使是在接收方窗口關(guān)閉的情況下,TCP是如何發(fā)送緊急數(shù)據(jù)的。在主機(jī)bsdi上啟動sock程序,并使之在連接建立后和從網(wǎng)絡(luò)讀取前暫停10秒種(通過使用-P選項),這將使另一端填滿發(fā)送窗口:

bsdi % sock -i -s -P10 5555

接著我們在主機(jī)sun上啟動客戶,使之使用一個8192字節(jié)的發(fā)送緩存(使用-S選項)并進(jìn)行6個向網(wǎng)絡(luò)寫1024字節(jié)數(shù)據(jù)的操作(使用-n選項)。還指明-U5選項,告知它向網(wǎng)絡(luò)寫第5個緩存之前要寫1個字節(jié)的數(shù)據(jù),并進(jìn)入緊急數(shù)據(jù)方式。我們指明詳細(xì)標(biāo)志來觀察寫的順序:

我們設(shè)置發(fā)送緩存為8192個字節(jié),以便讓發(fā)送應(yīng)用程序能夠立即寫所有的數(shù)據(jù)。圖20-14顯示了tcpdump輸出的這個交換過程的結(jié)果(刪去了連接建立的過程)。第1~5行表示發(fā)送方用4個1024字節(jié)的報文段去填充接收方的窗口。然后由于接收方的窗口被填滿(第4行的ACK確認(rèn)了數(shù)據(jù),但并沒有移動窗口的右邊沿),所以發(fā)送方停止發(fā)送。

在寫了第4個正常數(shù)據(jù)之后,應(yīng)用進(jìn)程寫了1個字節(jié)并進(jìn)入緊急方式。第6行是該應(yīng)用進(jìn)程寫的結(jié)果,緊急指針被設(shè)置為4098。盡管發(fā)送方不能發(fā)送任何數(shù)據(jù),但緊急指針和URG標(biāo)志一起被發(fā)送。

5個這樣的ACK在13 ms內(nèi)被發(fā)送(第6~10行)。第1個ACK在應(yīng)用進(jìn)程寫1個字節(jié)并進(jìn)入緊急方式時被發(fā)送,后面兩個在應(yīng)用進(jìn)程寫最后兩個1024字節(jié)的數(shù)據(jù)時被發(fā)送(盡管TCP不能發(fā)送這2048個字節(jié)的數(shù)據(jù),可每次當(dāng)應(yīng)用程序執(zhí)行寫操作的時候,TCP的輸出功能被調(diào)用。當(dāng)TCP看到正處于緊急方式時,它會發(fā)送其他的緊急通知)。第4個ACK在應(yīng)用進(jìn)程關(guān)閉其TCP連接時被發(fā)送(TCP的輸出功能再次被調(diào)用)。發(fā)送應(yīng)用程序在啟動幾毫秒后終止—在接收方應(yīng)用進(jìn)程已經(jīng)發(fā)出其第一個寫操作之前。TCP將所有的數(shù)據(jù)進(jìn)行排隊,并在可能時發(fā)送出去(這就是為何指明發(fā)送緩存為8192字節(jié)的原因,因此只有這樣才能夠把所有的數(shù)據(jù)都放置在緩存中)。第5個ACK很可能是在接收第4行的ACK時產(chǎn)生的。發(fā)送TCP很可能在這個ACK到達(dá)前便已將其第4個報文段放入隊列以便輸出(第5行)。另一端接收到這個ACK也會引起TCP輸出例程被調(diào)用。


圖20-14 tcpdump對TCP緊急方式的輸出結(jié)果

接著,接收方確認(rèn)最后的1024字節(jié)的數(shù)據(jù)(第11行),但同時通告窗口為0。發(fā)送方用一個包含緊急通知的報文段進(jìn)行了響應(yīng)。

在第13行,當(dāng)應(yīng)用進(jìn)程被喚醒、并從接收緩存讀取一些數(shù)據(jù)時,接收方通告窗口為2048字節(jié)。于是后面又發(fā)送了兩個1024字節(jié)的報文段(第14和15行)。其中,由于緊急指針在第1個報文段的范圍內(nèi),因此這個報文段被設(shè)置了緊急通知標(biāo)志,而第2個報文段則關(guān)閉了該標(biāo)志。

當(dāng)接收方再次打開窗口(第16行)時,發(fā)送方傳輸最后的數(shù)據(jù)(序號為6145)并發(fā)起正常的連接關(guān)閉。

圖20-15顯示了發(fā)送的6145個字節(jié)數(shù)據(jù)的序號??梢钥吹疆?dāng)進(jìn)入緊急方式時所發(fā)送的字節(jié)的序號是4097,但在圖20-14中緊急指針指向4098,這證明了該實現(xiàn)(SunOS 4.1.3)將緊急指針設(shè)置為緊急數(shù)據(jù)最后字節(jié)的下一個字節(jié)。


圖20-15 緊急方式例子中,應(yīng)用進(jìn)程的寫操作和TCP的一些報文段

該圖還可以讓我們觀察TCP是如何對應(yīng)用進(jìn)程寫的數(shù)據(jù)進(jìn)行重新分組化的。當(dāng)進(jìn)入緊急方式時待輸出的1個字節(jié)是與在緩存中的后面1023個字節(jié)一同發(fā)送的。下一個報文段也包含1024字節(jié)的數(shù)據(jù),而最后一個報文段則只包含一個字節(jié)。

20.9 小結(jié)

正如我們在本章一開始時講的那樣,沒有一種單一的方法可以使用TCP進(jìn)行成塊數(shù)據(jù)的交換。這是一個依賴于許多因素的動態(tài)處理過程,有些因素我們可以控制(如發(fā)送和接收緩存的大?。?,而另一些我們則沒有辦法控制(如網(wǎng)絡(luò)擁塞、與實現(xiàn)有關(guān)的特性等)。在本章,我們已經(jīng)考察了許多TCP的傳輸過程,介紹了所有我們能夠看到的特點和算法。

進(jìn)行成塊數(shù)據(jù)有效傳輸?shù)淖钪匾姆椒ㄊ荰CP的滑動窗口協(xié)議。我們考察了TCP為使發(fā)送方和接收方之間的管道充滿來獲得最可能快的傳輸速度而采用的方法。我們用帶寬時延乘積衡量管道的容量,并分析了該乘積與窗口大小之間的關(guān)系。在24.8節(jié)介紹TCP性能的時候?qū)⒃俅紊婕斑@個概念。

我們還介紹了TCP的PUSH標(biāo)志,因為在跟蹤結(jié)果中總是觀察到它,但我們無法對它的設(shè)置與否進(jìn)行控制。本章最后一個主題是TCP的緊急數(shù)據(jù),人們常常錯誤地稱其為“帶外數(shù)據(jù)”。TCP的緊急方式只是一個從發(fā)送方到接收方的通知,該通知告訴接收方緊急數(shù)據(jù)已被發(fā)送,并提供該數(shù)據(jù)最后一個字節(jié)的序號。應(yīng)用程序使用的有關(guān)緊急數(shù)據(jù)部分的編程接口常常都不是最佳的,從而導(dǎo)致更多的混亂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 21.1 引言 TCP提供可靠的運輸層。它使用的方法之一就是確認(rèn)從另一端收到的數(shù)據(jù)。但數(shù)據(jù)和確認(rèn)都有可能會丟失。T...
    張芳濤閱讀 3,053評論 0 8
  • 24.1 引言 TCP已經(jīng)在從1200 b/s的撥號SLIP鏈路到以太數(shù)據(jù)鏈路上運行了許多年。在80年代和90年代...
    張芳濤閱讀 1,522評論 0 3
  • 傳輸層-TCP, TCP頭部結(jié)構(gòu) ,TCP序列號和確認(rèn)號詳解 TCP主要解決下面的三個問題 1.數(shù)據(jù)的可靠傳輸...
    抓兔子的貓閱讀 4,553評論 1 46
  • 個人認(rèn)為,Goodboy1881先生的TCP /IP 協(xié)議詳解學(xué)習(xí)博客系列博客是一部非常精彩的學(xué)習(xí)筆記,這雖然只是...
    貳零壹柒_fc10閱讀 5,092評論 0 8
  • 1.這篇文章不是本人原創(chuàng)的,只是個人為了對這部分知識做一個整理和系統(tǒng)的輸出而編輯成的,在此鄭重地向本文所引用文章的...
    SOMCENT閱讀 13,134評論 6 174