1. 滑動窗口算法
滑動窗口算法工作過程如下。首先,發送方爲每1幀賦一個序號(sequence number),記作S e q N u m。現在,讓我們忽略S e q N u m是由有限大小的頭部字段實現的事實,而假設它能無限增大。發送方維護3個變量:發送窗口大小(send window size),記作S W S,給出發送方能夠發
注意,在這個例子中,接收方可以在幀7剛一到達時就爲幀6發送一個認幀N A K(negative acknowl edgment)。然而,由於發送方的超時機制足以發現這種情況,發送N A K反而爲發送方增加了複雜性,因此不必這樣做。正如我們已提到的,當幀7和8到達時爲幀5發送一個額外的A C K是合理的;在某些情況下,發送方可以使用重複的A C K作爲一個幀丟失的線索。這兩種方法都允許早期的分組丟失檢測,有助於改進性能。
關於這個方案的另一個變種是使用選擇確認(selective acknowledgements)。即,接收方能夠準確地確認那些已收到的幀,而不只是確認按順序收到最高序號的幀。因此,在上例中,接收方能夠確認幀7、8的接收。如果給發送方更多的信息,就能使其較容易地保持管道滿載,但增加了實現的複雜性。
2. 有限順序號和滑動窗口
現在我們再來討論算法中做過的一個簡化,即假設序號是可以無限增大的。當然,實際上是在一個有限的頭部字段中說明一個幀的序號。例如,一個3比特字段意味着有8個可用序號0 ~ 7。因此序號必須可重用,或者說序號能迴繞。這就帶來了一個問題:要能夠區別同一序號的不同次發送實例,這意味着可用序號的數目必須大於所允許的待確認幀的數目。例如,停止等待算法允許一次有1個待確認幀,並有2個不同的序號。
假設序號空間中的序號數比待確認的幀數大1,即S W S ≤ M A a x S e q N u m -1 ,其中M a x Seq N u m 是可用序號數。這就夠了嗎?答案取決於RW S 。如果RW S = 1,那麼MaxSeqNum≥SWS+1是足夠了。如果RW S等於S W S,那麼有一個只比發送窗口尺寸大1的M a x S e q N u m是不夠的。爲看清這一點,考慮有8個序號0 ~ 7的情況,並且S W S = RW S = 7。假設發送方傳輸幀0 ~ 6,並且接收方成功接收,但A C K丟失。接收方現在希望接收幀7,0 ~ 5,但發送方超時,然後發送幀0 ~ 6。不幸的是,接收方期待的是第二次的幀0 ~ 5,得到的卻是第一次的幀0 ~ 5。這正是我們想避免的情況。
結果是,當RW S = S W S時,發送窗口的大小不能大於可用序號數的一半,或更準確地說,SWS<(Maxseqnum+1)/2直觀地,這說明滑動窗口協議是在序號空間的兩半之間變換,就像停止等待協議的序號是在0和1之間變換一樣。唯一的區別是,它在序號空間的兩半之間連續滑動而不是離散的變換。
注意,這條規則是特別針對RW S = S W S的。我們把確定適用於RW S和S W S的任意值的更一般的規則留做一個練習。還要注意,窗口的大小和序號空間之間的關係依賴於一個很明顯以至於容易被忽略的假設,即幀在傳輸中不重新排序。這在直連的點到點鏈路上不能發生,因爲在傳輸過程中一個幀不可能趕上另一個幀。然而,我們將在第5章看到用在一個不同環境中的滑動窗口算法,並且需要設計另一條規則。
下面的例程說明我們如何實現滑動窗口算法的發送和接收的兩個方面。該例程取自一個正在使用的協議,稱爲滑動窗口協議S W P(Sliding Window Pro t o c o l)。爲了不涉及協議圖中的鄰近協議,我們用H L P(高層協議)表示S W P上層的協議,用L I N K(鏈路層協議)表示S W P下層的協議。我們先定義一對數據結構。首先,幀頭部非常簡單:它包含一個序號( S e q N u m)和一個確認號( A c k N u m)。它還包含一個標誌( F l a g s)字段,表明幀是一個A C K幀還是攜帶數據的幀。
對於協議的接收方,如前所述,該狀態包含變量L F R ,加上一個存放已收到的錯序幀的隊列(r e c v Q)。最後,雖然未顯示,發送方和接收方的滑動窗口的大小分別由常量S W S和RW S表示。
這個例程的另一個複雜性是使用s e m Wa i t 和s e n dW i n d o w N o t F u l l 信號量。S e n dWi n d o w N o t F u l l被初始化爲發送方滑動窗口的大小S W S(未給出這一初始化)。發送方每傳輸一幀, s e m Wa i t操作將這個數減1,如果減小到0,則阻塞發送方進程。每收到一個A C K,在d e l i v e r S W P中調用s e m S i g n a l操作(見下面)將此數加1,從而激活正在等待的發送方進程。
在繼續介紹S W P的接收方之前,需要調整一個看上去不一致的地方。一方面,我們說過,高層協議通過調用s e n d操作來請求低層協議的服務,所以我們就希望通過S W P發送消息的協議能夠調用s e n d(S W P, p a c k e t)。另一方面,用來實現S W P的發送操作的過程叫做s e n d S W P,並且它的第一個參數是一個狀態變量( S w p S t a t e)。結果怎樣呢?答案是,操作系統提供了粘結代碼將對s e n d的一般調用轉化爲對s e n d S W P的特定協議調用的粘結代碼。這個粘結代碼將s
e n d的第一個參數(協議變量S W P)映射爲一個指向s e n d S W P的函數指針和一個指向S W P工作時所需的協議狀態的指針。我們之所以通過一般函數調用使高層協議間接調用特定協議函數,是因爲我們想限制高層協議中對低層協議編碼的信息量。這使得將來能夠比較容易地改變協議圖的配置。現在來看d e l i v e r操作的S W P的特定協議實現,它在過程d e l i v e r S W P中實現。這個例程實際上處理兩種不同類型的輸入消息:本結點已發出幀的A C K和到達這個結點的數據幀。在某種意義上,這個例程的ACK部分是與send
SWP中所給算法的發送方相對應的。通過檢驗頭部的F l a g s字段可以確定輸入的消息是ACK還是一個數據幀。注意,這種特殊的實現不支持數據幀中捎帶A C K。當輸入幀是一個ACK時,delive rSWP僅僅在發送隊列(send Q)中找到與此ACK相應的位置(slot),取消超時事件,並且釋放保存在那一位置的幀。由於A C K可能是累積的,所以這項工作實際上是在一個循環中進行的。對於這種情況值得注意的另一個問題是子例程swp In Wind o w的調用。這個子例程在下面給出,它確保被確認幀的序號是在發送方當前希望收到的A
C K的範圍之內。
當輸入幀包含數據時, d e l i v e r S W P首先調用m s g S t r i p H d r和l o a d _ s w p _ h d r以便從幀中提取頭部。例程l o a d _ s w p _ h d r對應着前面討論的s t o r e _ s w p _ h d r,它將一個字節串轉化爲容納S W P頭部的C語言數據結構。然後d e l i v e r S W P調用s w p I n Wi n d o w以確保幀序號在期望的序號範圍內。如果是這樣,例程在已收到的連續的幀的集合上循環,並通過調用d
e l i v e r H L P例程將它們傳給上層協議。它也要向發送方發送累積的A C K,但卻是通過在接收隊列上循環來實現的(它沒有使用本節前面給出的s e q N u m To A c k變量)。
滑動窗口算法的第三個功能是,它有時支持流量控制(f l o w c o n t ro l),它是一種接收方能夠控制發送方使其降低速度的反饋機制。這種機制用於抑制發送方發送速度過快,即抑制傳輸比接收方所能處理的更多的數據。這通常通過擴展滑動窗口協議完成,使接收方不僅確認收到的幀,而且通知發送方它還可接收多少幀。可接收的幀數對應着接收方空閒的緩衝區數。在按序傳遞的情況下,在將流量控制併入滑動窗口協議之前,我們應該確信流量控制在鏈路層是必要的。
尚未討論的一個重要概念是系統設計原理,我們稱其爲相關性分離(separation of concerns)。即,你必須小心區別有時交織在一種機制中的不同功能,並且你必須確定每一個功能是必要的,而且是被最有效的方式支持的。在這種特定的情況下,可靠傳輸、按序傳輸和流量控制有時組合在一個滑動窗口協議裏,我們應該問問自己,在鏈路層這樣做是否正確。帶着這樣的疑問,我們將在第3章(說明X. 2 5網如何用它實現跳到跳的流量控制)和第5章(描述T C P如何用它實現可靠的字節流信道)重新考慮滑動窗口算法。