基於UDP實現可靠傳輸


UDP要想可靠,就要接收方收到UDP之后回復個確認包,發送方有個機制,收不到確認包就要重新發送,每個包有遞增的序號,接收方發現中間丟了包就要發重傳請求,當網絡太差時候頻繁丟包,防止越丟包越重傳的惡性循環,要有個發送窗口的限制,發送窗口的大小根據網絡傳輸情況調整,調整算法要有一定自適應性。恭喜你, 你在應用層重新實現了TCP!來自知乎-UDP如何實現可靠傳輸?姚冬的回答
也就是需要序列號、確認應答、超時重傳、以及發送窗口和擁塞窗口,這不就是TCP嗎,為什么還要自己實現一遍呢?

為什么需要用UDP實現可靠傳輸

  1. TCP建立連接慢,需要三次握手,對於大量的短連接更加不划算
  2. 如果你建立一次通訊只為了傳輸很少量的一整塊數據,那么明顯是一種浪費。這也是為什么 google 的 QUIC 對傳統的 http over TCP 有改善的空間。
  3. TCP主要慢在擁塞控制上,一是慢啟動擁塞窗口初始值太小,二是丟包以后擁塞窗口變得太小,但這有一些新的擁塞控制算法bbr等
  4. RTO翻倍vs不翻倍:
    TCP超時計算是RTOx2,這樣連續丟三次包就變成RTOx8了,十分恐怖,而KCP啟動快速模式后不x2,只是x1.5(實驗證明1.5這個值相對比較好),提高了傳輸速度。
  5. 選擇性重傳 vs 全部重傳:
    TCP 的重傳機制在數據包丟失時可能會重新傳輸已經成功接收的數據段,造成帶寬的浪費;
    TCP丟包時會全部重傳從丟的那個包開始以后的數據,KCP是選擇性重傳,只重傳真正丟失的數據包。
  6. 快速重傳:
    發送端發送了1,2,3,4,5幾個包,然后收到遠端的ACK: 1, 3, 3,當第一次收到ACK3時,KCP知道2被跳過1次,收到再ACK3時,知道2被跳過了2次,此時可以認為2號丟失,不用等超時,直接重傳2號包,大大改善了丟包時的傳輸速度。(和TCP類似,只是從3次變成2次)
  7. 延遲ACK vs 非延遲ACK:
    TCP為了充分利用帶寬,延遲發送ACK(NODELAY都沒用),這樣超時計算會算出較大 RTT時間,延長了丟包時的判斷過程。KCP的ACK是否延遲發送可以調節。

Delay Ack
簡單的說,Delay Ack就是延時發送ACK,在收到數據包的時候,會檢查是否需要發送ACK,如果需要的話,進行快速ACK還是延時ACK,在無法使用快速確認的條件下,就會使用Delay Ack。

TCP在何時發送ACK的時候有如下規定:
1.當有響應數據發送的時候,ACK會隨着數據一塊發送
2.如果沒有響應數據,ACK就會有一個延遲,以等待是否有響應數據一塊發送,但是這個延遲一般在40ms~500ms之間,一般情況下在40ms左右,如果在40ms內有數據發送,那么ACK會隨着數據一塊發送,對於這個延遲的需要注意一下,這個延遲並不是指的是收到數據到發送ACK的時間延遲,而是內核會啟動一個定時器,每隔200ms就會檢查一次,比如定時器在0ms啟動,200ms到期,180ms的時候data來到,那么200ms的時候沒有響應數據,ACK仍然會被發送,這個時候延遲了20ms.
3.如果在等待發送ACK期間,第二個數據又到了,這時候就要立即發送ACK!

優點:減少了數據段的個數,提高了發送效率
缺點:過多的delay會拉長RTT

  1. UNA vs ACK+UNA:
    ARQ模型響應有兩種,UNA(此編號前所有包已收到,如TCP)和ACK(該編號包已收到),光用UNA將導致全部重傳,光用ACK則丟失成本太高,以往協議都是二選其一,而 KCP協議中,除去單獨的 ACK包外,所有包都有UNA信息。

連續ARQ協議不會響應每個數據段,而是僅僅響應編號最大的這個數據段,表示之前的數據都收到了,這個叫做UNA模式,而停等ARQ協議可以看作是ACK模式。

  1. 非退讓流控:
    TCP 的擁塞控制在發生丟包時會進行退讓,減少能夠發送的數據段數量,但是丟包並不一定意味着網絡擁塞,更多的可能是網絡狀況較差;
    KCP正常模式同TCP一樣使用公平退讓法則,即發送窗口大小由:發送緩存大小、接收端剩余接收緩存大小、丟包退讓及慢啟動這四要素決定。但傳送及時性要求很高的小數據時,可選擇通過配置跳過后兩步,僅用前兩項來控制發送頻率(相當於忽略擁塞窗口)。以犧牲部分公平性及帶寬利用率之代價,換取了開着BT都能流暢傳輸的效果。

  2. 緩存控制:避免緩存積累延遲
    參考kcp-如何避免緩存積累延遲
    當前發送且沒有得到 ACK/UNA確認的數據,都會滯留在發送緩存中,一旦滯留數據超過了發送窗口大小限制,則該鏈接的 tcp send 調用將會 被阻塞,或者返回:EAGAIN / EWOULDBLOCK,這時候說明當前 tcp 信道可用帶寬已經趕不上你的發送速度了。
    可用帶寬 = min(本地可用發送窗口大小,遠端可用接收窗口大小) * (1 - 丟包率) / RTT
    重設窗口大小
    要解決上面的問題首先對你的使用帶寬有一個預計,並根據上面的公式重新設置發送窗口和接收窗口大小。你寫后端,想追求tcp的性能,也會需要重新設置tcp的 sndbuf, rcvbuf 的大小,KCP 默認發送窗口和接收窗口大小都比較小而已。

  • 不設置的話,如果默認 snd_wnd 太小,網絡不是那么順暢,你越來越多的數據會滯留在 snd_queue里得不到發送,你的延遲會越來越大。
  • 設定了 snd_wnd,遠端的 rcv_wnd 也需要相應擴大,並且不小於發送端的 snd_wnd 大小,否則設置沒意義。
    其次對於成熟的后端業務,不管用 TCP還是 KCP,你都需要實現相關緩存控制策略:
    緩存控制:傳送文件
    你用 tcp傳文件的話,當網絡沒能力了,你的 send調用要不就是阻塞掉,要不就是 EAGAIN,然后需要通過 epoll 檢查 EPOLL_OUT事件來決定下次什么時候可以繼續發送。
    KCP 也一樣,如果 ikcp_waitsnd 超過閾值,比如2倍 snd_wnd,那么停止調用 ikcp_send,ikcp_waitsnd的值降下來,當然期間要保持 ikcp_update 調用。
    同時,如果你能做的更好點,waitsnd 超過閾值了,代表一段時間內網絡傳輸能力下降了,此時你應該動態降低視頻質量,減少碼率,等網絡恢復了你再恢復。
    緩存控制:游戲控制數據
    大部分邏輯嚴密的 TCP游戲服務器,都是使用無阻塞的 tcp鏈接配套個 epoll之類的東西,當后端業務向用戶發送數據時會追加到用戶空間的一塊發送緩存,比如 ring buffer 之類,當 epoll 到 EPOLL_OUT 事件時(其實也就是tcp發送緩存有空余了,不會EAGAIN/EWOULDBLOCK的時候),再把 ring buffer 里面暫存的數據使用 send 傳遞給系統的 SNDBUF,直到再次 EAGAIN。
    那么 TCP SERVER的后端業務持續向客戶端發送數據,而客戶端又遲遲沒能力接收怎么辦呢?此時 epoll 會長期不返回 EPOLL_OUT事件,數據會堆積再該用戶的 ring buffer 之中,如果堆積越來越多,ring buffer 會自增長的話就會把 server 的內存給耗盡。因此成熟的 tcp 游戲服務器的做法是:當客戶端應用層發送緩存(非tcp的sndbuf)中待發送數據超過一定閾值,就斷開 TCP鏈接,因為該用戶沒有接收能力了,無法持續接收游戲數據。
    使用 KCP 發送游戲數據也一樣,當 ikcp_waitsnd 返回值超過一定限度時,你應該斷開遠端鏈接,因為他們沒有能力接收了。
    但是需要注意的是,KCP的默認窗口都是32,比tcp的默認窗口低很多,實際使用時應提前調大窗口,但是為了公平性也不要無止盡放大(不要超過1024)。
    總結
    緩存積累這個問題,不管是 TCP還是 KCP你都要處理,因為TCP默認窗口比較大,因此可能很多人並沒有處理的意識。
    當你碰到緩存延遲時:
  1. 檢查 snd_wnd, rcv_wnd 的值是否滿足你的要求,根據上面的公式換算,每秒鍾要發多少包,當前 snd_wnd滿足條件么?
  2. 確認打開了 ikcp_nodelay,讓各項加速特性得以運轉,並確認 nc參數是否設置,以關閉默認的類 tcp保守流控方式。
  3. 確認 ikcp_update 調用頻率是否滿足要求(比如10ms一次)。
  4. 當ikcp_waitsnd超過閾值時停止發送,或降低視頻碼率,或斷開連接等,根據不同場景采取不同策略

參考鏈接

  1. TCP-IP詳解:Delay ACK
  2. 為什么 TCP 協議有性能問題
  3. [https://github.com/skywind3000/kcp]
  4. 基於UDP實現的可靠傳輸協議(比如uTP),與TCP協議相比有什么優缺點? - 韋易笑的回答 - 知乎


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM