1. 程式人生 > >計算機網路實驗-可靠資料傳輸協議-GBN協議的設計與實現

計算機網路實驗-可靠資料傳輸協議-GBN協議的設計與實現

這一週做了一個計算機網路的實驗,名字叫 可靠資料傳輸協議-GBN協議的設計與實現
感覺自己做的很認真,實現的效果也不錯,就把自己的過程與結果記錄一下

對於這個實驗,實驗要求上說實現SR協議是加分項,而SR協議又是以GBN協議為基礎,所以自己直接一步到位,只實現了 SR 協議(偷了個懶 ) (>ω<)
首先先粘最最重要的資源,包括完整原始碼和實驗報告 : gitbub專案地址

以下內容擷取自實驗報告:(提取有用資訊)

GBN的原理與實現

原理:

GBN是屬於傳輸層的協議,它負責接收應用層傳來的資料,將應用層的資料報傳送到目標IP和埠

滑動視窗: 假設在序號空間內,劃分一個長度為N的子區間,這個區間內包含了已經被髮送但未收到確認的分組的序號以及可以被立即傳送的分組的序號,這個區間的長度就被稱為視窗長度。(隨著傳送方方對ACK的接收,視窗不斷的向前移動,並且視窗的大小是可變的)

GBN一個分組的傳送格式是 Base(1Byte) + seq(1Byte) + data(max 1024Byte)

GBN協議的傳送流程是: 從上層應用層獲得到一個完整的資料報,將這個資料報進行拆分(一個GBN資料幀最大傳輸的資料大小限制為1024B,因為在乙太網中,資料幀的MTU為1500位元組,所以UDP資料報的資料部分應小於1472位元組(除去IP頭部20位元組與UDP頭的8位元組)),如果傳送方的滑動視窗中,如果視窗內已經被髮送但未收到確認的分組數目未達到視窗長度,就將視窗剩餘的分組全部用來發送新構造好的資料,剩餘未能傳送的資料進行快取。傳送完視窗大小的資料分組後,開始等待接收從接收方發來的確定資訊(ACK),GBN協議採取了累積確認,當傳送方收到一個對分組n的ACK的時候,即表明接收方對於分組n以及分組n之前的分組全部都收到了。對於已經確認的分組,就將視窗滑動到未確認的分組位置(視窗又有空閒位置,可以傳送剩餘分組了),對於未確認的分組,如果計時器超時,就需要重新發送,直到收到接收方的ACK為止。
對於超時的觸發,GBN協議會將當前所有已傳送但未被確認的分組重傳,即如果當前視窗內都是已傳送但未被確認的分組,一旦定時器發現視窗內的第一個分組超時,則視窗內所有分組都要被重傳。每次當傳送方收到一個ACK的時候,定時器都會被重置。
接收方只需要按序接收分組,對於比當前分組序號還要大的分組則直接丟棄。假設接收方正在等待接收分組n,而分組n+1卻已經到達了,於是,分組n+1被直接丟棄,所以傳送方並不會出現在連續傳送分組n,分組n+1之後,而分組n+1的ACK卻比分組n的ACK更早到達傳送方的情況。

實現:

傳送方:

首先定義視窗大小,起始 base 的值, 視窗採用連結串列的資料結構儲存
private int WindowSize = 16;
private long base = 0;
進入一個迴圈,迴圈結束條件是所有需要傳送的資料都已經發送完成,並且視窗中的分組都已經全部確認。
在這個迴圈中,如果視窗內有空餘,就開始傳送分組,直到視窗被佔滿,計時器開始計時,之後進入接收ACK的狀態,收到ACK之後,更新滑動視窗的位置,之後如果計時器超時,就將視窗內所有的分組全部重發一次。之後開始下一次迴圈。

接收方:

不需要有快取,只需要記錄一個seq值,每成功接收一個數據幀,seq+1,開始迴圈順序接收資料幀,對於seq不是目標值得資料幀直接丟棄,如果是符合要求的資料幀,就給傳送方傳送一個ACK=seq的確認資料幀,直到傳送方沒有資料傳來為止。
GBN的實現就完成了。

SR協議的原理與實現:

SR協議的原理:

SR協議是在GBN協議的基礎上進行的改進。
對於SR協議來說,傳送方需要做到:
為每一個已傳送但未被確認的分組都需要設定一個定時器,當定時器超時的時候只發送它對應的分組。
當傳送方收到ACK的時候,如果是視窗內的第一個分組,則視窗需要一直移動到已傳送但未未確認的分組序號。
對於接收方,需要設定一個視窗大小的快取,即使是亂序到達的資料幀也進行快取,併發送相應序號的ACK, 並及時更新視窗的位置,視窗的更新原則同傳送方。

SR協議實現:

傳送方:

在GBN傳送方的基礎上,增加一個基於連結串列資料結構的計時器,對每一個未被確認的分組進行計時。在每次判斷是否超時時,需要對連結串列中所有的計時進行判斷,與GBN重傳不同的是,SR只對超時的那一個分組進行重傳。

傳送方完整程式碼見gitbub專案中 SR.java中的void send(byte[] content) 函式

接收方:

需要增加一個同傳送方的對分組的快取,用於快取亂序到達的分組,同樣使用連結串列資料結構。
List datagramBuffer = new LinkedList<>();
首先進入一個迴圈, 一次迴圈需要進行如下工作:
接收分組,將分組的資料快取到datagramBuffer對應的位置(因為到達的資料可能是亂序的)
然後傳送資料分組對應seq的ACK,告知傳送方自己已經成功接收。 之後更新滑動視窗的位置,更新的規則同傳送方一樣。之後進行下一次迴圈。
直到傳送方沒有新的資料傳來,超過接收方設定的最大時間,就結束迴圈,將接收到的資料拼接成一個完整的Byte陣列,傳給應用層。

接收方的完整程式碼見github專案中 SR.java中的 ByteArrayOutputStream receive() 函式

SR協議的實現就完成了。

雙向傳輸的實現:

傳送方傳送資料需要佔用一個固定的埠,而接收方也需要一個固定的埠來向傳送方傳送 ACK,所以就可以封裝一個完整的協議類,類似於TCP的有連線傳輸一樣,傳送方和接收方之間在兩個固定的ip和埠之間進行資料的傳輸,直到雙方的傳輸結束。傳送方在使用send()函式進行傳送時,也可以同時使用receive()函式進行接收,兩個過程並不衝突,可以同時進行。如果要同時收發,就需要同時開一個傳送執行緒和一個接收執行緒,兩個執行緒獨立執行,沒有衝突,這樣就可以實現雙向資料傳輸了。
所以我構造了一個SR class,其中包含的成員變數有:

private InetAddress host;
private int targetPort, ownPort;
private int WindowSize = 16;
private final int sendMaxTime = 2, receiveMaxTime = 4; // max time for one datagram
private long base = 0;
private final int virtualLossRemainder = 17; // this value is used to simulate the loss of the datagram as a remainder

包含的函式有兩個:

void send(byte[] content) // 負責資料的傳送
ByteArrayOutputStream receive()  // 負責資料的接收
private ByteArrayOutputStream getBytes(List<ByteArrayOutputStream> buffer, long max) //負責將接收到的資料分組拼接成一個完整的資料報
private boolean checkWindow(List<Integer> timers) 負責判斷當前的視窗是否可以移動

詳細的程式碼見github專案中SR.java

在Client 主函式中先使用SR協議傳送一張圖片, 在Server 主函式中使用SR協議接收這張圖片,並儲存。然後向Client傳送另一張圖片, Client由傳送變成接收。
這有就可以實現雙向檔案的傳送和接收了。
詳細的程式碼見github專案中 Client.java 中 main函式和Server.java中的main函式

模擬丟包

在接收端,設立一個計數變數count, 然後每次收到資料幀就加一,如果count 對一個數取餘=0就不傳送ACK,模擬這一分組丟失的情況,然後測試傳送方會不會重新發送丟失的分組。
這一部分的程式碼實現詳見github專案中 SR.java中 receive中 count這個變數。