本章將介紹可靠數據傳輸協議的原理,具體過程為,通過給出越來越復雜的傳輸服務要求,不斷迭代基礎的“可靠傳輸協議”,最終得到真正可用的可靠傳輸協議。
一、可靠數據傳輸概述
如圖1.1 是可靠數據傳輸的框架。為上層實體提供到的服務抽象是:數據可以通過一條可靠的信道進行傳輸。借助於可靠信道,傳輸數據比特不會受到損壞或丟失,而且所有數據都是按發送順序交付的。
圖1.1 可靠數據傳輸:服務模型和服務實現
可靠數據傳輸協議(reliable data transfer protocol)實現可靠傳輸服務,但其下層協議可能是不可靠的。因此在本節討論中,我們將底層直接視為不可靠的點對點通信。同時我們將考慮越來越復雜的底層信道,這里貫穿我們始終的一個假設是分組將以它們發送的次序進行交付(即順序交付),而某些分組可能丟失;這就是說,底層信道不會對分組重排序(在后面,我們統一使用“分組”而不用運輸層術語“報文段”)。
在描述中,我們將采用圖1.1b中的數據傳輸協議的接口。調用rdt_send( )函數,上層可以調用數據傳輸協議的發送方,它將要發送的數據交付給位於接收方的較高層;在接收端,當分組從信道的接收端到達時,將調用rdt_rcv( )。當rdt協議想要向較高層交付數據來,將通過調用deliver_data( )來完成;而rdt的發送端和接收端都要通過調用udt_send( )發送分組給對方。(rdt表示可靠數據傳輸協議,udt表示不可靠數據傳輸協議,可見對任何一個開發選好名字是多么重要)。
本節中描述中,僅考慮單向數據傳輸(unidirectional data transfer)的情況,即數據傳輸是從發送端到接收端的。不考慮雙向數據傳輸(bidirectional data transfer)。不過需要注意的是,雖然只考慮單向數據傳輸,但協議也需要在發送端和接收端兩個方向上傳輸分組。
二、構造可靠數據傳輸協議
現在我們就先通過建立簡單的可靠數據傳輸協議,再提出不同的情況,一步步迭代得到最后的可靠的數據傳輸協議。
2.1 經完全可靠信道的可靠數據傳輸:rdt1.0
首先考慮最簡單情況,即底層信道是完全可靠的(即比特不會受損或丟失,數據按發送順序交付)。圖2.1顯示了rdt1.0發送方和接收方的有限狀態機(Finite-State Machine,FSM)的定義。圖2.1a中的FSM定義了接收方的操作,b中的FSM定義了接收方操作。需要注意,發送方和接收方有各自的FSM。FSM描述圖中的箭頭指示了協議從一個狀態變遷到另一個狀態,圖2.1中發送方和接收方的FSM每個都只有一個狀態,因此變遷過程必定是從一個狀態返回到自身。引起變遷的事件顯示在表示變遷的橫線上方,時間發生所采取的動作顯示在橫線下方。如果對一個事件沒有動作,或沒有就事件發生而采取一個動作,就用∧表示,以分別明確地表示缺少動作或事件。FSM的初始狀態用虛線表示。
圖2.1 rdt1.0:用於完全可靠信道的協議
如圖2.1,rdt的發送端只通過rdt_send (data)事件接受來自較高層的數據,產生一個包含該數據的分組(通過make_pkt (data)動作),並將分組發送到信道中。實際上,rdt_send (data)事件是由較高層應用的過程調用產生的。
在接收端,rdt通過rdt_rcv (packet)事件從底層信道接收一個分組,從分組中取出的數據(通過extract (packet,data)動作),並將數據上傳給較高層(通過deliver_data (data)動作)。實際上,rdt_rcv (packet)事件是由較底層協議的過程調用產生的。
在本協議中,所有分組由於是完全可靠信道,接收端不需要提供任何反饋信息,不用擔心出錯。
2.2 經具有比特差錯信道的可靠數據傳輸:rdt2.0
底層信道更為實際的模型是分組中的比特可能受損的模型,這里我們繼續假定所有發送的分組不會丟失,所有分組(雖然比特可能受損)將按其發送的順序被接收。在當前迭代版本,我們增加肯定確認(positive acknowledgment)和否定確定(negative acknowledgment)兩種控制報文,使接收方可以讓發送方知道哪些內容被正確接收,哪些內容接收有誤並因此需要重傳。基於這樣重傳機制的可靠數據傳輸協議稱為自動重傳請求(Automatic Repeat reQuest,ARQ)協議。
ARQ協議中還需要另外三種協議功能來處理存在比特差錯的情況:
- 差錯檢測。事實上除了差錯檢測,還有糾錯技術,這些技術所需的額外比特被匯集在rdt2.0數據分組的分組校驗和字段中。
- 接收方反饋。引入的“肯定確認”(ACK)和“否定確認”(NAK)分組就是為了使接收方能就分組是否被正確接收進行反饋。理論上,這些反饋分組只需要一個比特長:如0表示NAK,1表示ACK。
- 重傳。接收方收到有差錯的分組時,發送方將重傳該分組。
圖2.2 rdt2.0:用於具有比特差錯信道的協議
如圖2.2的發送端有兩個狀態。在左邊的狀態中,發送端協議正等待來自上層的數據,當rdt_send (data)事件出現時,發送方將產生一個包含待發送數據的分組(snpkt),帶有校驗和,然后經由udt_send (sndpkt)操作發送。在右邊的狀態,發送方協議等待來自接收方的ACK或NAK分組,如果收到ACK分組,則發送方知道最近發送的分組已被正確接收,因此協議返回到等待來自上層數據的狀態;如果收到NAK分組,該協議重傳上一個分組並等待接收方為響應重傳分組而回送的ACK和NAK。需要注意的是:當發送方處於ACK或NAK狀態時,它不能從上層獲得更多的數據,即rdt_send ( )事件不可能出現,僅當接收到ACK並離開該狀態時才能發生rdt_send ( )事件。因此,發送方將不會發送一塊新數據,廢除發送方確認接收方已正確接收到當前分組,由於這種行為,rdt2.0被稱為停等(stop-and-wait)協議。
rdt2.0接收方的FSM仍只有單一狀態。當分組到達時,接收方取決於分組是否受損,來回答一個ACK或NAK。
2.2.1 rdt2.0的修訂版:rdt2.1
rdt2.0存在一個致命缺陷,我們沒有考慮ACK/NAK分組受損的情況以及糾正ACK/NAK分組的中的差錯。ACK/NAK分組受損,可以通過增加校驗和比特檢測,但更重要的是,當ACK/NAK分組受損,發送方無法知道接收方是否正確接收了上一塊發送的數據,就無法進行下一步響應。
對於ACK/NAK受損,我們考慮三種處理辦法的可能性:
- 第一種,當ACK/NAK受損,發送方就不知道接收方表達的意思,這時發送方回復一個新的分組(引入新分組),表達發送方不知道接收方表達的意思,接收方則再次復述剛才的分組內容(重發反饋分組)。但是如果這個新引入的分組也產生了差錯呢?那么接收方就不知道發送方要表達的意思,很可能也回復這個新的分組內容,表達接收方不知道發送方的意思。顯然,這條路很崎嶇。
- 第二種是增加足夠的檢驗和比特,使發送方不僅能檢測差錯,還可以恢復差錯。對於會產生差錯但不會丟失分組的信道,這就能直接解決問題。
- 第三種是,當發送方收到受損的ACK/NAK分組,不知道接收方意思時,只需要重傳當前分組即可。不過這種方法在發送發到接收方的信道中引入了冗余分組(duplicate packet)。冗余分組的麻煩是接收方不知道它上次發送的ACK或NAK是否被發送方正確地收到,因此它無法事先知道接收到的分組是新的還是一次重傳。
解決這個問題的辦法是在數據分組中添加一個新字段(現在幾乎所有的數據傳輸協議均采用此方法),讓發送方對其數據分組編號,即將發送數據的序號(sequence number)放在該字段。這樣,接收方就能知道發送方是否正在重傳前一個分組(接收到的分組序號和最近收到的分組序號相同),或是一個新分組(序號變化,用模2運算“前移”)。同時因為我們假設信道不丟失分組,所以ACK和NAK本身不需要指明它們要確認的分組序號,發送方知道所接收到的ACK和NAK是為了響應其最近發送的數據分組而生成的。
圖2.3 rdt2.1 發送方
圖2.4 rdt2.1 接收方
rdt2.1發送方和接收方FSM的狀態數都是以前的兩倍,這些狀態是為了反映發送發正在發送的分組或接收方希望接收到的分組的序號是0還是1。這里0和1就是分組序號,因為是停等協議,只有發送方確認上一分組被正確接收才會發送下一分組,所以不需要用如1、2、3……來標識分組,只用表明此分組與上一個分組是否同一分組即可。
rdt2.1仍使用肯定確認和否定確認,當接收到失序的分組時,接收方對所接收到的分組發送一個肯定確認(注意:在2.0中我們假設分組按順序交付,所以這里的失序不是交付失序。它出現的情況是,接收方收到發送方分組后返回ACK,接收方進入下一狀態,但是ACK在信道中受損,發送方收到受損的反饋后重發分組,接收方再次收到同一分組,知道ACK反饋出錯,因此扔掉這個分組,重新返回ACK讓發送方進入下一狀態);如果收到受損分組,則接收方返回一個否定確認。如果不發送NAK,而是對上次正確接收的分組發送一個ACK,我們也能實現和NAK相同的效果(因為發送方接收到對同一個分組的兩個ACK(即收到冗余ACK(duplicate ACK) )后,就知道接收方沒有正確接收跟在被確認兩次的分組后面的分組)。
2.2.2 無NAK的可靠數據傳輸協議:rdt2.2
rdt2.2也是在有比特差錯信道上實現的,它與rdt2.1的細微變化是,接收方此時必須包括由一個ACK報文所確認的分組序號(在接收方FSM中,在make_pkt( )中包括參數ACK 0或ACK 1實現),發送方此時必須檢查接收到的ACK報文中被確認的分組序號(在發送方FSM中,在isACK( )中包括參數0或1來確認)。
圖2.5 rdt2.2 發送方
圖2.6 rdt2.2 接收方
2.3 經具有比特差錯的丟包信道的可靠數據傳輸:rdt3.0
現在我們假定除了比特受損,底層信道還會丟包,但所有分組仍會按其發送的順序被接收。在rdt2.2中我們使用了檢驗和、序號、ACK分組和重傳,現在我們需要在此基礎上解決丟包問題。假定發送方傳輸一個數據分組,該分組或接收方對該分組的ACK發生了丟失,在這兩種情況下,發送方都收不到應當到來的接收方的響應。在當前版本,我們讓發送方負責檢測和恢復丟包工作。如果發送方願意等待足夠長的時間以便確定分組已丟失,則它只需要重傳該數據分組即可。
因此我們至少需要讓發送方等待這樣一個時間:發送方與接收方之間的一個往返時延加上接收方處理一個分組所需要的時間,來讓發送方確定分組已丟失。即如果在這個時間內沒有收到ACK,則重傳該分組。但也可能發生這樣一個情況,一個分組及其ACK都沒有丟失,而是經歷一個很大的時延,發送方也會重傳該分組。這就在發送方到接收方的信道中引入了冗余數據分組,不過這一點已經在rdt2.2中通過序號被解決了。
為了實現這樣一個基於時間的重傳機制,需要引入一個倒計數定時器(countdown timer)。為此,發送方需要做到:每次發送一個分組(包括第一次分組和重傳分組)時,便啟動一個定時器;響應定時器中斷,采取適當動作;終止定時器。
圖2.7 rdt3.0發送方
如圖2.7是rdt3.0的發送方FSM,因為超時重傳機制是在發送方實現的,因此接收方的FSM與rdt2.0的沒什么差別。我們看具體的rdt3.0的運行情況,如圖2.8,時間從圖的頂部朝底部移動,發送方括號部分表明了定時器的設置時刻以及隨后的超時。因為分組序號在0和1之間交替,因此rdt3.0有時被稱為比特交替協議(alternating-bit protocal)
圖2.8 rdt3.0的運行,比特交替協議
至此我們使用了檢驗和、序號、定時器、肯定和否定確認分組,解決了比特出錯和丟包問題,得到了一個可靠數據傳輸協議。但該協議是一個停等協議,發送方只有等到接收方正確收到后才會發送下一個分組,發送方利用率(定義為:將分組發送進信道的時間/發送進信道到發送下一個分組間隔時間)太低,因此我們需要一個更高效率的解決方案。
三、流水線可靠數據傳輸協議
解決停等協議帶來的效率問題的一個簡單的方法:不以停等方式運行,允許發送方發送多個分組而無需等待確認,如圖3.1。因為許多從發送方向接收輸送的分組可以被看成是填充到一條流水線中,故這種技術被稱為流水線(pipelining)。如圖3.2顯示了如果發送方可以在等待確認之前發送3分報文,其利用率也基本上提高3倍。
圖3.1 停等協議與流水線協議
圖3.2 停等和流水線發送
要實現流水線協議,需要對可靠傳輸協議進行改進:
- 必須增加序號范圍,因為每個輸送中的分組必須有一個唯一的序號,而且也許有多個在輸送中的未確認的報文。
- 協議的發送方和接收方兩端不得不緩存多個分組。發送方最低限度應當能緩沖那些已發送但沒有確認的分組,接收方或許也需要緩存那些已正確接收的分組。
- 所需序號范圍和對緩沖的要求取決於傳輸協議如何處理丟失、損壞及延時過大的分組。解決這些差錯恢復的基本方法有兩種:回退N步(Go-Back-N,GBN)和選擇重傳(Selective Repeat,SR)。
四、回退N步
在回退N步(GBN)協議中,允許發送方發送多個分組(當有多個分組可用時)而不需等待確認,但它也受限於在流水線中未確認的分組數不能超過某個最大允許數N。實現GBN協議依賴於一個滑動窗口,GBN也常被稱作滑動窗口協議(sliding-window protocol)。
這是一個虛擬的窗口,窗口長度(window size)為N,具體定義為:我們將基序號(base)定義為最早未確認分組的序號,將下一個序號(nextseqnum)定義為最小的未使用序號(即下一個帶發送分組的序號),則可將序號范圍分成4端。在[0,base-1]段內的序號對應於已發送並被確認的分組;[base,nextseqnum-1]段內對應已發送但未被確認的分組,[nextseqnum,base+N-1]段內的序號能用於那些要被立即發送的分組,如果有數據來自上層的話;最后大於或等於base+N的序號是不能使用的,直到當前流水線中未被確認的分組(特別是序號為base的分組)已得到確認為止。
這里我們將被發送的、未被確認的分組數目限制為N,進行這種限制,是為了對發送方進行流量控制,避免無限制的發送對網絡造成擁塞。
圖4.1 在GBN中發送方看到的序號
在具體實踐中,一個分組的序號承載在分組首部的一個固定長度的字段中。如果分組序號字段的比特數是k,則該序號范圍是[0,2^k-1]。在一個有限的序號范圍內,所有涉及序號的運損必須使用模2^k運算(即序號空間可被看做是一個長度為2^k的環,其中序號2^k-1緊接着序號0)。而在TCP中有一個32比特的序號字段,其中的TCP序號是按字節流中的字節數進行計數的,而不是按分組計數,這一點在講TCP時會進行說明。
圖4.2 GBN發送方的擴展FSM描述
如圖4.2,GBN發送方必須響應三種類型的事件:
- 上層的調用。當上層調用rdt_send ( )時,發送方首先檢查發送窗口是否已滿,即是否有N個已發送但未被確認的分組。如果窗口未滿,則產生一個分組並將其發送,並相應地更新變量。如果窗口已滿,發送方只需要將數據返回上層,隱式地指示上層窗口已滿,然后上層可能會過一會再試。在實際中,發送方更可能緩存這些數據,或者使用同步機制(如信號量或一個標志)允許上層在僅當窗口不滿是才調用rdt_send ( )。
- 收到一個ACK。在GBN協議中,對序號為n的分組的確認采取累計確認(cumulative acknowledgement)的方式,表明接收方已正確收到序號為n的以前且包括n在內的所有分組。這一點會在接收方繼續說明。
- 超時事件。協議名“回退N步”來源於出現丟失或時延過長分組時發送方的處理行為。這里定時器將用於恢復數據或確認分組丟失。如果出現超時,發送方重傳所有已發送但未被確認的分組所使用的定時器。如果收到一個ACK,但仍有已發送但未被確認的分組,則定時器被重新啟動。如果沒有已發送但未被確認的分組,停止該定時器。
再來看發送方的FSM描述,在rdt3.0中,因為是停等協議,所以每次發送一個,為這一個分組設置一個定時器。但流水線協議,發送的分組是多個,不可能為每個分組設置一個定時器,沒必要而且資源開銷太大。因此在rdt_send ( )時,if (base==nextseqnum)判斷中啟動了一個定時器,它可以被看做是最早的已發送但未被確認的分組所使用的定時器。發送方只使用了一個定時器。
圖4.3 GBN接收方的擴展FSM描述
如圖4.3,GBN的接收方動作,如果一個序號為n的分組被正確接收到,並且按序(即上次交付給上層的數據是序號為n-1的分組),則接收方為分組n發送一個ACK,並將該分組中的數據部分交付到上層。其他所有情況,接收方丟棄該分組,並為最近按序接收的分組重新發送ACK。注意到因為一次交付給上層一個分組,如果分組k已接收並交付,則所有序號比k小的分組也已經交付,即交付數據是按發送順序接收的,這也是采用累積確認的原因。
結合發送方和接收方的FSM再看,發送方流水線式發送,接收方按順序接收,每次接收都是傳給上層的分組都是緊接着上一個分組,若不是則為最近按序接收的分組重新發送ACK。發送方收到反饋base=getacknum (rcvpkt)+1,因為返回的序號是最近分組到的序號,所以base不會變就不會等於nextseqnum,表示仍有已發送未被確認的分組,重新啟動start_timer。如果發送方收到的反饋分組出錯,就什么也不做,等待超時。超時后就發送所有未被確認分組(注意base可能接受到部分確認的序號,因此啟動不是從頭,而是從第一個未被確認分組開始)。
圖4.4 運行中的GBN
如圖例4.4,窗口長度為4個分組的GBN協議的運行情況,發送方首先發送分組0~3,然后在繼續發送之前,必須等待直到一個或多個分組被確認。當接收到每一個連續的ACK(如ACK 0、ACK 1)是,該窗口便向前滑動,發送方就可以發送新的分組(分組4和分組5)。在接收方,分組2丟失,因此在分組3到達時發現不是與1連續的分組,就丟棄分組3並發送ACK 1(分組1)。在發送方未超時重新發送分組2,並接收到分組2的反饋分組之前,其余到達接收方的分組都會被丟棄。
GBN協議中,接收方丟棄了所有的失序分組,即使該分組是正確的。這樣做的原因是,接收方必須按序將分組交付給上層,若分組n丟失而n+1到達了,如果接收方緩存n+1分組,由於GBN的重傳規則,n和n+都會被重傳。因此接收方直接丟棄n+1分組即可,這樣就不必緩存失序,使得接收方維護簡單,但實際上很浪費網絡。
五、選擇重傳
選擇重傳(SR)是為了避免“GBN中單個分組出錯或丟失就會引起大量的分組被重傳”的問題,選擇重傳通過讓發送方僅重傳那些它懷疑在接收方出錯的分組而避免不必要的重傳。與GBN相同的是,SR也采用窗口來限制未確認的分組。
圖5.1 選擇重傳發送方和接收方的序號空間
如圖5.1,SR的發送方和接收方都有窗口。接收方將確認一個正確接收的分組而不管其是否失序,失序的分組將被緩存直到所有丟失的分組(即序號更小的分組)都被收到為止,這時才可以將一批分組按序交付給上層(按序交付規則)。
SR發送方的事件和動作:
(1)從上層收到數據。當從上層接收到數據后,SR發送方檢查下一個可用於該分組的序號。如果序號位於發送方的窗口內,則將數據打包並發送;否則就像在GBN中一樣,要么將數據緩存,要么將其返回給上層以便以后傳輸。
(2)超時。仍采用定時器防止丟失分組,但是與GBN不同的是,現在每個分組必須擁有自己的邏輯定時器,因為超時發生后只能發送該超時的分組。可以使用單個硬件定時器模擬多個邏輯定時器的操作。
(3)收到ACK。如果收到ACK,倘若該分組序號在窗口內,則SR發送方將那個確認的分組標記為已接收。如果該分組序號等於send_base(即窗口內第一個發送的分組被正確接收),則窗口基序號向前移動到具有最小序號的未被確認的分組處。如果窗口移動了並且有序號落在窗口內的未發送分組,則發送這些分組。
SR接收方的事件和動作:
(1)序號在[rcv_base,rcv_base+N-1]內的分組被正確接收。此情況下,收到的分組落在接收方的窗口內,一個選擇ACK被反饋給發送方。如果該分組以前沒有收到過,則緩存該分組。如果該分組的序號等於接收窗口的基序號,則該分組以及以前緩存的序號連續的分組交付給上層。然后,接收窗口按向前移動分組的編號並向上交付這些分組。
(2)序號在[rcv_base-N,rcv_base-1]內的分組被正確接收到。此情況下,必須產生一個ACK,即使該分組是接收方以前已確認過的分組。
(3)其他情況。忽略該分組。
圖5.2 SR操作
圖5.2說明的是SR在丟包時的操作,在該圖中,接收方初始時緩存了分組3、4、5,並在最終受到分組2時,才將它們一並交付。
需要注意的是SR接收方的第二步操作很重要,接收方重新確認(而不是忽略)已收到過的序號小於當前窗口基序號的分組。這是很重要的,例如,若發送方分組send_base的ACK丟失,沒有從接收方傳回發送方,則發送方最終將重傳該分組(即使接收方此時已經收到過該分組)。如果接收方不確認該分組,則發送方窗口將永遠不能向前滑動。這是很重要到的一個方面,接收方和發送方的窗口並不總一致。
圖5.3 SR接收方窗口太大的困境:是一個新分組還是一次重傳
圖5.3是我們要注意的另一個問題——有限序號。在該圖例有包括4個分組序號0、1、2、3的有限序號范圍且窗口長度為3。假定發送了分組0至2,並在接收方被正確接收且確認,此時接收方窗口落在第4、5、6個分組上,其序號3、0、1。這時有兩種情況:第一種如圖5.3a,對前3個分組的ACK丟失,因此發送方重傳這些分組,那么此時接收方要接收序號為0的分組;第二種情況如圖5.3b,前三個分組的ACK被發送方正確接收,因此發送方向前移動窗口並發送第4、5、6個分組,其序號為3、0、1,此時假設序號3丟失,序號0到達。
圖5.3中的兩種情況,接收方都無法區分是第一個分組的重傳還是第5個分組的初次傳輸。顯然這樣的窗口長度和序號的安排無法使協議運行,因此需要設置合理的窗口長度,對SR協議,窗口長度必須小於或等待序號空間大小的一半。
解決了窗口大小的問題,還有最后一個問題——重新排序。之前我們假設的是分組不會在信道中被重排序。但現實是鏈路中存在緩存,可能某個分組由於過大時延,此時發送方已重傳並且接收方已確認,在雙方的窗口中都已經沒有包含該分組時,該分組到達接收方或發送方。對於這樣的冗余分組,實際中是通過假定一個分組在網絡中的“存活時間”不會超過某個固定最大時間量來做的(不同協議可能實現不同)。
六、總結:可靠傳輸機制及其用途
(1)檢驗和:用於檢測在一個分組中的比特錯誤
(2)定時器:用於超時/重傳一個分組,可能因為該分組(或其ACK)在信道中丟失了。由於當一個分組延時但未丟失,或當一個分組已被接收方收到,但從接收方到發送方的ACK丟失時,可能產生超時事件,所以接收方可能會收到一個分組的多個冗余版本。
(3)序號:用於為從發送方流向接收方的數據分組按順序編號。所接收分組的序號間的空隙可使接收方檢測出丟失的分組。具有相同序號的分組可使接收方檢測出一個分組的冗余副本。
(4)確認:接收方用於告訴發送方一個分組或一組分組已被正確接收到。確認報文通常攜帶着被確認的分組或多個分組的序號。確認可以是逐個的或累積的,這取決於協議。
(5)否定確認:接收方用於告訴發送方一個分組或一組分組未被正確接收到。
(6)窗口、流水線:發送方也許被限制僅發送那些序號落在一個指定范圍內的分組。通過允許一次發送多個分組但未被確認,發送方的利用率可在停等操作模式的基礎上的到增加。窗口長度可根據接收方接收和緩存報文的能力、網絡中的擁塞程度或兩者情況來進行設置。


















