不管哪類場景,都是要保證可靠性,也就是質量,那么在UDP之上怎么實現可靠呢?答案就是重傳。
重傳模式
IP協議在設計的時候就不是為了數據可靠到達而設計的,所以UDP要保證可靠,就依賴于重傳,這也就是我們通常意義上的RUDP行為,在描述RUDP重傳之前先來了解下RUDP基本框架,如圖:
圖2
RUDP在分為發送端和接收端,每一種RUDP在設計的時候會做不一樣的選擇和精簡,概括起來就是圖中的單元。RUDP的重傳是發送端通過接收端ACK的丟包信息反饋來進行數據重傳,發送端會根據場景來設計自己的重傳方式,重傳方式分為三類:定時重傳,請求重傳和FEC選擇重傳。
定時重傳
定時重傳很好理解,就是發送端如果在發出數據包(T1)時刻一個RTO之后還未收到這個數據包的ACK消息,那么發送就重傳這個數據包。這種方式依賴于接收端的ACK和RTO,容易產生誤判,主要有兩種情況:
對方收到了數據包,但是ACK發送途中丟失。
ACK在途中,但是發送端的時間已經超過了一個RTO。
所以超時重傳的方式主要集中在RTO的計算上,如果你的場景是一個對延遲敏感但對流量成本要求不高的場景,就可以將RTO的計算設計比較小,這樣能盡最大可能保證你的延時足夠小。例如:實時操作類網游、教育領域的書寫同步,是典型的用expense換latency和Quality的場景,適合用于小帶寬低延遲傳輸。如果是大帶寬實時傳輸,定時重傳對帶寬的消耗是很大的,極端情況會用20%的重復重傳率,所以在大帶寬模式下一般會采用請求重傳模式。
請求重傳
請求重傳就是接收端在發送ACK的時候攜帶自己丟失報文的信息反饋,發送端接收到ACK信息時根據丟包反饋進行報文重傳。如下圖:
圖3
這個反饋過程最關鍵的步驟就是回送ACK的時候應該攜帶哪些丟失報文的信息,因為UDP在網絡傳輸過程中會亂序會抖動,接收端在通信的過程中要評估網絡的jitter time,也就是rtt_var(RTT方差值),當發現丟包的時候記錄一個時刻t1,當t1 + rtt_var < curr_t(當前時刻),我們就認為它丟失了,這個時候后續的ACK就需要攜帶這個丟包信息并更新丟包時刻t2,后續持續掃描丟包隊列,如果他t2 + RTO <curr_t,再次在ACK攜帶這個丟包信息,以此類推,直到收到報文為止。這種方式是由丟包請求引起的重發,如果網絡很不好,接收端會不斷發起重傳請求,造成發送端不停的重傳,引起網絡風暴,通信質量會下降,所以我們在發送端設計一個擁塞控制模塊來限流,這個后面我們重點分析。除了網絡風暴以外,整個請求重傳機制也依賴于jitter time和RTO這個兩個時間參數,評估和調整這兩個參數和對應的傳輸場景也息息相關。請求重傳這種方式比定時重傳方式的延遲會大,一般適合于帶寬較大的傳輸場景,例如:視頻、文件傳輸、數據同步等。
FEC選擇重傳
除了定時重傳和請求重傳模式以外,還有一種方式就是以FEC分組方式選擇重傳,FEC(Forward Error Correction)是一種前向糾錯技術,一般是通過XOR類似的算法來實現,也有多層的EC算法和raptor涌泉碼技術,其實是一個解方程的過程。應用到RUDP上示意圖如下:
圖4
在發送方發送報文的時候,會根據FEC方式把幾個報文進行FEC分組,通過XOR的方式得到若干個冗余包,然后一起發往接收端,如果接收端發現丟包但能通過FEC分組算法還原,就不向發送端請求重傳,如果分組內包是不能進行FEC恢復的,就請求想發送端請求原始的數據包。FEC分組方式適合解決要求延時敏感且隨機丟包的傳輸場景,在一個帶寬不是很充裕的傳輸條件下,FEC會增加多余的冗余包,可能會使得網絡更加不好。FEC方式不僅可以配合請求重傳模式,也可以配合定時重傳模式。