目錄html
名詞解釋算法
問題 1: 接收方如何判斷數據包是否丟失?緩存
問題 2:發送方如何確認數據包已經丟失?安全
問題 3:重傳超時的計算規則?服務器
問題 4:發送方的數據包要緩存多久?微信
問題 5:接收端多久發送一次 nack 請求?網絡
問題 6:哪些丟失的數據包會放入 nack 請求隊列中?併發
問題 7:如何防止某個數據包頻繁的 nack 請求?app
問題 8:重傳包的優先級?FEC 包是否須要重傳?ide
問題 9:RTCP 協議的 NACK 報文是如何定義的?
名詞解釋
ACK:Acknowledgement,它是一種正向反饋,接收方收到數據後回覆消息告知發送方。
NACK:Negative Acknowledgement,則是一種負向反饋,接收方只有在沒有收到數據的時候才通知發送方。
REX:Retransmission,重傳,當發送方得知數據丟失後,從新發送一份數據。
問題 1:接收方如何判斷數據包是否丟失 ?
解決方案:編號,每個 packet 都打上一個序列號(Seq number),接收端發現序列號跳變/缺失,則能夠判斷數據包丟失了。
這就是爲何 TCP 協議的包頭(packet header)裏面要定義一個序列號字段的緣由(如圖紅色標記):
擴展一下,咱們再看看 UDP 協議的包頭(packet header)的定義:
能夠看到,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:發送方的數據包要緩存多久 ?
發送端爲了能實現重傳,必須在本地將發送的數據包緩存起來,在須要重傳的時候,便可從緩存隊列裏面取到該數據包進行重傳。
對於 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 裏面:
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 則是該類型報文的具體負載:
協議規定的 NACK 反饋報文的 PT= 205,FMT=1,FCI 的格式以下(能夠附帶多個 FCI,經過 header 的 length 字段來標示其長度):
這裏的設計比較巧妙,它能夠一次性攜帶多個連續的數據包的丟包狀況:
PID(Packet identifier),即爲丟失的 RTP 數據包的序列號
BLP(Bitmao of Lost Packets),經過掩碼的方式指出了接下來 16 個數據包的丟失狀況
小結
關於網絡通訊中關於 ACK/NACK/REX 相關知識點就分享到這裏了,若有疑問的小夥伴歡迎來信 lujun.hust@gmail.com 交流。另外,也歡迎你們關注個人新浪微博 @盧_俊 或者 微信公衆號 @Jhuster 獲取最新的文章和資訊。