談談網絡通信中的 ACK、NACK 和 REX

目錄

名詞解釋

問題 1: 接收方如何判斷數據包是否丟失?

問題 2:發送方如何確認數據包已經丟失?

問題 3:重傳超時的計算規則?

問題 4:發送方的數據包要緩存多久?

問題 5:接收端多久發送一次 nack 請求?

問題 6:哪些丟失的數據包會放入 nack 請求隊列中?

問題 7:如何防止某個數據包頻繁的 nack 請求?

問題 8:重傳包的優先級?FEC 包是否需要重傳?

問題 9:RTCP 協議的 NACK 報文是如何定義的?

名詞解釋

ACK:Acknowledgement,它是一種正向反饋,接收方收到數據後回覆消息告知發送方。

NACK:Negative Acknowledgement,則是一種負向反饋,接收方只有在沒有收到數據的時候才通知發送方。

REX:Retransmission,重傳,當發送方得知數據丟失後,重新發送一份數據。

問題 1:接收方如何判斷數據包是否丟失 ?

v2-bd7fc27d77a92a81e1335bec8b8abb7e_hd.png

解決方案:編號,每一個 packet 都打上一個序列號(Seq number),接收端發現序列號跳變/缺失,則可以判斷數據包丟失了。

這就是爲什麼 TCP 協議的包頭(packet header)裏面要定義一個序列號字段的原因(如圖紅色標記):

v2-eb978203bbaf84308d5feab3032eda6f_hd.png

擴展一下,我們再看看 UDP 協議的包頭(packet header)的定義:

v2-3b295afec2a4779fc8ac29aad2ed5318_hd.png

可以看到,UDP header 沒有用任何字段來標識序列號(Seq number),由此可見,UDP 是一個完全不關心是否丟包的傳輸協議。

問題 2:發送方如何確認數據包已經丟失 ?

有幾種常見的方案,這裏先簡單介紹下,不詳細展開細節。


1. 停等協議

發送方每次只發送一個包,同時啓動一個定時器。如果定時器超時依然沒有收到這個包的 ACK,則認爲丟包,重傳這個包。如果收到 ACK,則重置定時器併發送下一個包。

問題:丟包的判斷和傳輸效率非常低。


2. 連續 ARQ 協議 & 滑窗協議

發送方維持着一個一定大小的發送窗口,位於發送窗口內的所有包可以連續發送出去,中途不需要依次等待對方的 ACK 確認。

接收方通常採用 積累確認模式,即不必對每一個包逐個發送 ACK,而是在連續收到幾個包後,對順序到達的最後一個包序號發送 ACK,表示:這個包及之前的所有包都已正確收到了。

積累確認模式 的缺點:亂序比較嚴重的網絡下,效率非常低,部分已經送到但沒有按照順序送達的包也必須重傳。

改進方案:選擇性重傳(注:KCP/SRT 協議有實現):對於順序的包,發送積累確認;跳躍的包,發送 ACK;發送端只重傳真正丟失的數據包。


3. 快速重傳

使用 ACK 機制的傳輸協議,通常在發送端等到某個數據包的 ACK 超時後,纔會重傳數據包,不夠及時。

快速重傳:如果接收端接收到了序號跳躍的數據包,則立即給發送方發送最後一個連續的數據包的 ACK(重複確認) 。如果發送端收到連續 3 個重複確認,則認爲該 ACK 的下一個數據包丟失了,並立即重傳該丟失的數據包。


4. NACK

接收方定時把所有未收到的包序號通過反饋報文通知到發送方進行重傳。

帶來的改進:減少的反饋包的頻率和帶寬佔用,同時也能比較及時地通知發送方進行丟包重傳。

問題 3:重傳超時的計算規則 ?

RTO:重傳超時時間(Retransmission Timeout),它是發送端用來判斷數據包丟失和執行重傳的最重要的一個參數。

很明顯,它應該是一個隨網絡傳輸的 RTT(往返時間)而變化的值,理想情況下,RTO 的值不小於 RTT 即可(從數據包發送到對方的 ACK 到達的最短時間),實際情況下,RTT 變化是非常頻繁的,每一次傳輸的 RTT 可能都不一樣,如果粗暴地設置 RTO = RTT 則一定會導致重傳過度頻繁。

因此 TCP 協議採用的 RTO 計算方法是:

1. 基於多次 RTT 測量,給出一個平滑後的 RTT 預估值:SRTT(Smoothed Round Trip Time)

2. RTO = SRTT + 某種係數(防止抖動的閾值)

3. 進一步,系統級別設置 RTO 的下限爲 100ms 或 200ms,防止異常值

4. 更進一步,對於重傳包的 RTO,加上一個退避算法,比如,每重傳一次,則 RTO = 2 RTO 這樣的方式來減少對一個包進行頻繁的重傳

注:關於重傳包 RTO 的退避策略,KCP 經過實驗證明 ,RTO 的退避係數使用 1.5 倍的效果比 2 倍的效果更好。

問題 4:發送方的數據包要緩存多久 ?

v2-657e8ec469ad6ad41bd9470ca618099c_hd.png

發送端爲了能實現重傳,必須在本地將發送的數據包緩存起來,在需要重傳的時候,即可從緩存隊列裏面取到該數據包進行重傳。

對於 ACK 模型的傳輸協議(如:TCP),在收到對方的 ACK 之後刪除緩存即可,那如果是 NACK 模型的傳輸協議,如何更新和清理這個緩存隊列呢 ?


1. 方案一:基於 RTT 和 NACK 時間間隔


假設當前的 RTT(網絡往返時間)是  rtt ms,NACK 的反饋時間間隔是 x ms,那麼,一個數據包在發送緩存隊列中最少的存活時間應該是:

cache time = 2 * rtt + x

假設在這個時間後內收到的 NACK 反饋包沒有指出該數據包丟失,則可以刪除了。當然,類似 RTO 所涉及到的問題原因,rtt 是頻繁變化的,因此單純依靠這個理論值來刪除緩存並不安全,建議增加一定的冗餘。


2. 方案二:基於業務場景


對於實時音視頻通信場景,對延時有一定的要求,因此,超過 1s 的數據,就沒有必要再重傳了。或者,假設視頻的 GOP 是 2s,那麼,最多在緩存隊列保持 2s 的數據包即可。

問題 5:接收端多久發送一次 nack 請求 ?

假設接收端發現數據包發生序列號跳躍後立即發送 NACK 請求,由於 UDP 數據包可能會亂序到達,因此這種方案會導致過多的無效重傳請求。

更加合理的方案是:每間隔指定的時間(比如:WebRTC 使用的是 10ms)發送一次 NACK 請求,一次性帶上這段時間所有的丟包序號。

問題 6:哪些丟失的數據包會放入 nack 請求隊列中 ?

接收端的重傳請求的隊列也應該有一定的機制,不是所有的丟包都必須要求重傳,比如:

1. 當前音頻播放到了 timestamp 爲 x 的時間點了,其實在 timestamp > x 的所有丟失的音頻包都不應該再請求重傳了,視頻也是如此。

2. 作爲 SFU 中轉服務器,它沒有播放時間的概念,因此方法 1 並不適用,但是可以參考發送端緩存的邏輯,假設 GOP 是 2s,則對比最新的 packet 時間戳,丟失的數據包時間在 2s 之前的數據,則沒有必要再申請重傳了。

補充一點,作爲 SFU 中轉服務器,可能會遇到下述情況,即:收到客戶端的 NACK 請求的數據包不再自己的 cached packet list 裏面:

v2-315b346666c5c617f019aa3203210561_hd.png

SFU 作爲客戶端上行的接收端,發現丟包也跟普通的接收端一樣,定時主動地向源頭髮送 NACK 請求;反過來,SFU 作爲客戶端下行的發送端,收到 NACK 請求後,如果發現不在 cached list,則標記一下,一旦收到源頭的重傳,則第一時間轉發到下行。

問題 7:如何防止某個數據包頻繁的 nack 請求 ?

參考 WebRTC 的實現,有如下防止策略:

1. 當一個丟失的包被 NACK 請求重傳了至少 N 次(如:10次)後依然沒有成功收到,則應該放棄了(很可能發送端也已經沒有這個數據包的緩存了)

2. 考慮到重傳請求在發送端的響應時間及網絡 RTT,接收端應該確保在一定時間週期內不要頻繁地發送對同一個數據包的 NACK 重傳請求。(如:WebRTC 選擇的時間週期是 5 + RTT * 1.5),即:在這個時間週期內不再重複發送同一個數據包的 NACK 請求。

3. 當 nack reqeust list 裏面的數據包太多了(比如:超過 1000),則應該考慮清理一下(網絡太弱了),對於視頻的話,直接發送 IDR request,重新申請新的 GOP 數據。

問題 8:重傳包的優先級?FEC 包是否需要重傳 ?

考慮到丟包 -> 重傳已經耽誤了數據包的達到時間了,因此,重傳包的優先級應該大於普通的數據包,當然,也應該有根據重傳次數優先級逐步遞減的策略。

FEC 包不需要,意義不大,FEC 的目的是爲了減少重傳而增加的冗餘包,丟掉沒有致命的影響,我們只需要重傳價值更大的數據包即可。

問題 9:RTCP 協議的 NACK 報文是如何定義的 ?

NACK 報文的定義在 [rfc4585] 文檔中定義。

RTCP 的反饋報文包頭定義如下,FMT 和 PT 決定了該報文的類型,FCI 則是該類型報文的具體負載:

v2-855bbe688464b97460cd0c313d1488fe_hd.png

協議規定的 NACK 反饋報文的 PT= 205,FMT=1,FCI 的格式如下(可以附帶多個 FCI,通過 header 的 length 字段來標示其長度):

v2-009b6ba8d39a2839cff34ecf0dd50222_hd.png

這裏的設計比較巧妙,它可以一次性攜帶多個連續的數據包的丟包情況:

  • PID(Packet identifier),即爲丟失的 RTP 數據包的序列號

  • BLP(Bitmao of Lost Packets),通過掩碼的方式指出了接下來 16 個數據包的丟失情況

小結

關於網絡通信中關於 ACK/NACK/REX 相關知識點就分享到這裏了,如有疑問的小夥伴歡迎來信 [email protected] 交流。另外,也歡迎大家關注我的新浪微博 @盧_俊 或者 微信公衆號 @Jhuster 獲取最新的文章和資訊。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章