22.1簡(jiǎn)介
我們已經(jīng)知道接收者會(huì)在窗口大小中向發(fā)送者通告一個(gè)適當(dāng)?shù)臄?shù)據(jù)數(shù)量,TCP通過這種方法來(lái)進(jìn)行流量控制。當(dāng)窗口大小變成0時(shí)將會(huì)發(fā)生什么情況?它將有效地阻止發(fā)送者向另一端傳輸數(shù)據(jù),直到窗口大小變成非零。
我們可以在圖20.3看到這種情況。段9打開了被段8關(guān)閉的窗口,當(dāng)發(fā)送者收到段9以后,它立即開始發(fā)送數(shù)據(jù)。TCP必須能夠處理打開窗口的確認(rèn)(段9)丟失的情況。確認(rèn)不是可靠傳輸?shù)模簿褪钦f(shuō),TCP不會(huì)對(duì)確認(rèn)進(jìn)行ACK,它只ACK包含數(shù)據(jù)的段。
附圖20.3
如果有確認(rèn)丟失,我們要結(jié)束兩端的互相等待:接收者等待接收數(shù)據(jù)(因?yàn)樗峁┙o發(fā)送者一個(gè)非零的窗口),發(fā)送者則在等待允許它發(fā)送的窗口更新(已丟失?。?。為了防止這種情況的死鎖發(fā)生,發(fā)送者使用了一個(gè)持續(xù)計(jì)時(shí)器(persiet timer)來(lái)周期性的詢問接收者是否已增加了窗口。從發(fā)送者發(fā)出的這些段稱為窗口探測(cè)(window probes)。在本章,我們研究窗口探測(cè)和持續(xù)定時(shí)器。我們同樣檢查和持續(xù)定時(shí)器相關(guān)的糊涂窗口綜合癥(silly window syndrome)。
22.2 范例
為了解工作中的持續(xù)定時(shí)器,我們將啟動(dòng)一個(gè)從客戶端監(jiān)聽連接請(qǐng)求的接收者進(jìn)程,接受連接請(qǐng)求,并且在從網(wǎng)絡(luò)讀取數(shù)據(jù)之前睡眠(sleep)很長(zhǎng)一段時(shí)間。
Sock程序的暫停選項(xiàng)-P可以使服務(wù)器在接受連接請(qǐng)求和執(zhí)行第一次讀之間睡眠。我們用如下方式運(yùn)轉(zhuǎn)服務(wù)器:
svr4 % sock -i -s -P100000 5555
這樣可以讓服務(wù)器在從網(wǎng)絡(luò)讀取數(shù)據(jù)之前睡眠100000秒(27.8個(gè)小時(shí))??蛻舳诉\(yùn)行在主機(jī)bsdi,并向服務(wù)器的5555端口寫1024字節(jié)的數(shù)據(jù)。圖22.1顯示了tcpdump輸出(我們已經(jīng)在輸出中去掉了連接的建立過程)

圖22.1持續(xù)定時(shí)器探測(cè)零大小窗口的例子
段1-13顯示了從客戶端到服務(wù)器的正常數(shù)據(jù)傳輸,用9216個(gè)字節(jié)的數(shù)據(jù)填充了窗口。服務(wù)器通告的窗口大小是4096,并有一個(gè)默認(rèn)的socket緩沖區(qū),大小也是4096。這是SVR4中TCP/IP代碼和數(shù)據(jù)流子系統(tǒng)(stream subsystem)之間交互的某種形式。(This is some form of interaction between the TCP/IP code and the streams subsystems in SVR4.)
在段13,服務(wù)器確認(rèn)了前面4個(gè)數(shù)據(jù)段,當(dāng)通告窗口大小是0,阻止了客戶端傳輸更多的數(shù)據(jù)。這將引起客戶端設(shè)置它的持續(xù)定時(shí)器。當(dāng)定時(shí)器期滿時(shí),如果客戶端未收到窗口更新,它就探測(cè)這個(gè)空窗口,看是否有窗口更新丟失了。這是因?yàn)槲覀兊姆?wù)器正在睡眠,那9216個(gè)字節(jié)被TCP放在緩沖區(qū)里,正等待著應(yīng)用程序?qū)λ鼈兊淖x取。
注意客戶端探測(cè)窗口間的時(shí)間間隔。第一個(gè)(段14)是在收到0大小窗口的4.949秒之后。下一個(gè)(段16)是4.996秒之后。往下的前后兩段的間隔大約是6,12,24,48和60秒。
為什么這些間隔總是比5,6,12,24,48和60少零點(diǎn)幾秒?這些探測(cè)是由TCP的500毫秒定時(shí)器期滿觸發(fā)的。當(dāng)定時(shí)器期滿時(shí),窗口探測(cè)被發(fā)出,并且在4毫秒之后收到回復(fù)。收到回復(fù)引起定時(shí)器的重新啟動(dòng),但到下一個(gè)時(shí)鐘tick的時(shí)間大約是(500-4)毫秒。(?)
使用標(biāo)準(zhǔn)的TCP指數(shù)后退(exponential backoff)來(lái)計(jì)算持續(xù)定時(shí)器Exponential 。對(duì)于一個(gè)典型的LAN,第一次超時(shí)的計(jì)算結(jié)果是1.5秒。第二次超時(shí)值是第一次的結(jié)果乘以2,即3秒。下一次乘以4,得到6,再往下乘以8,得到12……但是持續(xù)定時(shí)器總是在5到60秒之間,這就解釋了我們?cè)趫D22.1中的所見。
窗口探測(cè)包含了一個(gè)字節(jié)的數(shù)據(jù)(順序號(hào)9217)。TCP總是允許在已關(guān)閉窗口的結(jié)尾之外發(fā)送一個(gè)字節(jié)的數(shù)據(jù)。注意,盡管這樣,但返回的告知窗口大小為0的確認(rèn)并不ACK這個(gè)字節(jié)。(它們只ACK9216之前(包括9216)的字節(jié)。)因此該字節(jié)可以被持續(xù)的重傳。
持續(xù)狀態(tài)的特征和21章重傳超時(shí)的不同在于TCP從不會(huì)放棄發(fā)送窗口探測(cè)。這些窗口探測(cè)以60秒的時(shí)間間隔連續(xù)發(fā)送,直到窗口打開,或者連接被關(guān)閉。
22.3 糊涂窗口綜合癥(silly window syndrome)
TCP使用的這種基于窗口的流量控制機(jī)制,可能導(dǎo)致進(jìn)入一種叫做糊涂窗口綜合癥(SWS)的條件。當(dāng)它發(fā)生時(shí),小的數(shù)據(jù)通過連接被交換,而全長(zhǎng)(full-sized)段卻無(wú)法傳輸 [Clark1982] 。
連接兩端都可能引起這種情況:接收者通告小的窗口(而不是等待出現(xiàn)較大窗口再通告),發(fā)送端發(fā)送小的數(shù)據(jù)(而不是等待其它的數(shù)據(jù)來(lái)發(fā)送一個(gè)較大的段)??梢栽趦啥瞬扇≌_的措施來(lái)避免糊涂窗口綜合癥的發(fā)生。
1. 接收者不可以通告小窗口。通常的算法是,接收者不通告比當(dāng)前通告(可能是0)大的窗口,除非窗口可以增加一個(gè)全長(zhǎng)段(比如正在被接收的MSS),或者增加半個(gè)接收者緩沖區(qū)空間,其它情況都要小于當(dāng)前通告。(即等到窗口到一定大小后再通告。)
2. 發(fā)送者通過停止發(fā)送來(lái)避免糊涂窗口綜合癥,除非下列某個(gè)條件成立:(a)一個(gè)全長(zhǎng)段能夠被發(fā)送,(b)至少有對(duì)方曾經(jīng)通告的最大窗口一半的段能夠被發(fā)送,(c)不需要確認(rèn)(即沒有未被確認(rèn)的數(shù)據(jù))或者連接上Nagel算法(19.4)已被禁止時(shí),任何數(shù)據(jù)都可以被發(fā)送。
條件(b)用以處理總是通告小窗口(可能比段長(zhǎng)度更小)的情況,條件(c)阻止我們?cè)谟形幢淮_認(rèn)的數(shù)據(jù)(正在等待被確認(rèn)),以及Nagel算法被禁止時(shí)發(fā)送小段。如果應(yīng)用程序正在寫小數(shù)據(jù)(比如比段長(zhǎng)度更小),條件(c)可以避免糊涂窗口綜合癥。
這3個(gè)條件也需要我們回答這樣一個(gè)問題:當(dāng)有未被確認(rèn)的數(shù)據(jù)時(shí),如果有Nagel算法阻止我們發(fā)送小的段,那么到底多小才算???從條件(a)我們知道“小”意思是字節(jié)數(shù)少于段長(zhǎng)度。條件(b)只用在較老的原始主機(jī)。
步驟2中的條件(b)需要我們跟蹤由另一端通告的最大窗口長(zhǎng)度。這是發(fā)送者對(duì)對(duì)方接收緩沖區(qū)的大小的猜測(cè)嘗試。盡管接收者緩沖區(qū)在連接建立時(shí)可能減小,但事實(shí)上這很少
發(fā)生。
范例*
在發(fā)送主機(jī)sun使用sock程序向網(wǎng)絡(luò)寫6個(gè)1024字節(jié)的數(shù)據(jù)。
sun % sock -i -n6 bsdi 7777
在主機(jī)bsdi的接收進(jìn)程放置一些暫停,在第一次讀之前暫停4秒,在連續(xù)兩次讀之間暫停2秒。而且接收者每次讀256字節(jié)
bsdi % sock -i -s -P4 -p2 -r256 7777
初始的暫停是為了填滿接收者的緩沖區(qū),迫使發(fā)送者停止發(fā)送。由于接收者接著從網(wǎng)絡(luò)讀了少量數(shù)據(jù),我們期望能看到接收者執(zhí)行的糊涂窗口綜合癥的避免措施。
圖22.2是傳輸6144個(gè)字節(jié)數(shù)據(jù)的時(shí)間線。
圖22.2 顯示接收者避免糊涂窗口綜合癥的時(shí)間線
我們同樣需要跟蹤讀取數(shù)據(jù)的應(yīng)用程序在每個(gè)時(shí)間點(diǎn)的變化,接收緩沖區(qū)的當(dāng)前字節(jié)數(shù),以及緩沖區(qū)可用空間的字節(jié)數(shù)。圖22.3顯示了這些變化。

圖22.3接收者避免糊涂窗口綜合癥的事件順序
圖22.3 第一列是每個(gè)動(dòng)作發(fā)生的相對(duì)時(shí)間點(diǎn)。那些帶三位小數(shù)點(diǎn)的是從tcpdump輸出得到的(圖22.2)。那些小數(shù)點(diǎn)后為99的是接收端主機(jī)發(fā)生行為時(shí)的假想(assumed)時(shí)間。
當(dāng)從發(fā)送者收到數(shù)據(jù)時(shí),接收者的緩沖區(qū)的數(shù)據(jù)數(shù)量增加;而當(dāng)應(yīng)用程序從緩沖區(qū)讀取數(shù)據(jù)時(shí),數(shù)量減少。我們需要跟蹤的是接收者發(fā)給發(fā)送者的窗口通告,從中我們可以看到接收者避免糊涂窗口綜合癥的方法。
前四個(gè)數(shù)據(jù)段和相關(guān)的ACK(段1-5)顯示發(fā)送者填滿了接收者緩沖區(qū)。發(fā)送者被迫停止,但還有數(shù)據(jù)需要發(fā)送。它設(shè)置持續(xù)定時(shí)器的最小值為5秒。
當(dāng)持續(xù)定時(shí)器期滿時(shí),一個(gè)字節(jié)的數(shù)據(jù)被發(fā)送(段6)。由于接收端應(yīng)用程序已經(jīng)從緩沖區(qū)讀取了256字節(jié)(在時(shí)間3.99),因此該字節(jié)被接收并確認(rèn)(段7)。但通告的窗口仍是0,這是由于接收者還沒有騰出能夠容難一個(gè)全長(zhǎng)段或半個(gè)緩沖區(qū)大小的空間。這是接收端的糊涂窗口綜合癥的避免措施。
發(fā)送者的持續(xù)定時(shí)器被重置,并在5秒之后(在時(shí)間10.151)期滿,又有一個(gè)字節(jié)發(fā)送并被確認(rèn)(段8,9)。這時(shí)接收者緩沖區(qū)的可用空間是1022字節(jié),因此仍通告0窗口。
在時(shí)間15.151,定時(shí)器期滿,段10和11被發(fā)送和接收。此時(shí)可用空間為1533字節(jié),(大于全長(zhǎng)段1024)因此通告一個(gè)非零窗口。發(fā)送者立即使用這個(gè)窗口,發(fā)送1024個(gè)字節(jié)(段12)。對(duì)這1024個(gè)字節(jié)的確認(rèn)(段13)通告窗口大小是509字節(jié)。這似乎與我們前面看到的小窗口通告相矛盾(即為什么通知窗口大小不是0)。
這里段11通告一個(gè)1533字節(jié)的窗口,并且發(fā)送者只發(fā)送1024字節(jié)。如果段13通告的窗口大小是0,就與窗口不能通過左移右邊緣來(lái)收縮的TCP原則(20.3)相沖突。這就是為何通告509字節(jié)小窗口的原因。
接下來(lái)我們看,發(fā)送者沒有立即向小窗口發(fā)送數(shù)據(jù)。這是發(fā)送者糊涂窗口綜合癥的避免方法。相反地,它又等待了一個(gè)持續(xù)定時(shí)器時(shí)間(到時(shí)間20.151),發(fā)送了509字節(jié)數(shù)據(jù)。盡管它最終發(fā)送這509個(gè)字節(jié)的小數(shù)據(jù),但它在發(fā)送前等待了5秒,看是否有ACK到達(dá)以使窗口打開得更大些。這509個(gè)字節(jié)使得接收者緩沖區(qū)的可用空間剩下768字節(jié),(不到一個(gè)全長(zhǎng)度)因此確認(rèn)(段15)通告窗口大小是0。
在時(shí)間25.151,持續(xù)定時(shí)器期滿,發(fā)送者發(fā)送一個(gè)字節(jié)的數(shù)據(jù)(段16)。在段17接收者通告窗口大小1279字節(jié)。
發(fā)送者有額外511個(gè)字節(jié)需要發(fā)送(上次發(fā)送遺留下的),因此它在收到段17后立即發(fā)送這511個(gè)字節(jié)(段18)。該段也包括了一個(gè)FIN標(biāo)志,段19對(duì)數(shù)據(jù)和FIN進(jìn)行了確認(rèn),通告窗口為767字節(jié)。(1279-511=768,為什么通告窗口少了一個(gè)字節(jié)?被FIN消費(fèi)了)
發(fā)送端應(yīng)用程序在寫完第六段1024字節(jié)的數(shù)據(jù)后發(fā)出了close,它由ESTABLISHED狀態(tài),遷移到FIN_WAIT_1狀態(tài),再到FIN_WAIT_2狀態(tài)(圖18.12)。它保持這個(gè)狀態(tài),直到從另一端收到FIN。FIN_WAIT_2這個(gè)狀態(tài)沒有定時(shí)器,因?yàn)樗诙?/span>18中發(fā)送的FIN已經(jīng)由段19確認(rèn)。這就是我們?cè)谒盏綄?duì)方的FIN(段21)之前看不到其他傳輸?shù)脑颉?/span>
接收端的應(yīng)用程序繼續(xù)每隔兩秒地從緩沖區(qū)讀取256個(gè)字節(jié)的數(shù)據(jù)。為什么在時(shí)間39.99,段10被發(fā)送?當(dāng)應(yīng)用程序讀到39.99時(shí),接收者緩沖區(qū)空間已經(jīng)由上次通告的767字節(jié)(段19)上升到了2816。緩沖區(qū)增加的空間是2049空間。前面我們提到,接收者緩沖區(qū)空間增加它的一半時(shí),需要發(fā)送一個(gè)窗口更新。從中我們可以發(fā)現(xiàn),應(yīng)用程序從緩沖區(qū)讀取數(shù)據(jù)時(shí),接收端在時(shí)時(shí)地檢查是否該發(fā)送一個(gè)窗口更新。
在時(shí)間51.99,應(yīng)用程序執(zhí)行最后一次讀取,并收到一個(gè)文件結(jié)束標(biāo)志,因?yàn)榫彌_區(qū)已經(jīng)為空。最后兩個(gè)段被發(fā)送,連接終止。