雲風的 BLOG: 可靠 UDP 傳輸
本文分三個部分:一,什麼時候有可能採用 UDP 通訊而不是用 TCP 更好;二,一個可靠的 UDP 通訊模組的 API 介面該如何設計;三,一個簡單的實現。
首先,我一直是非常反對在 UDP 協議上實現一個可靠傳輸協議的,即類似 TCP over UDP 的東西。
TCP 已經夠複雜了,幾乎不太可能重新設計的更好。如果用 UDP 再實現一個可靠傳輸協議,而表現的比 TCP 效果更好,那麼多半隻是在部分情況下的優勢;或是霸道的佔用了過量的資源,而 TCP 在設計時則是很友好的,以整個網路的通暢為更高準則的。
對於後者,我心裡相當排斥。如果大家都想獨佔網路頻寬,那麼只會讓每個人都無法獲得高質量通訊。
在網路遊戲,尤其是行動網路上的網路遊戲製作圈裡,不斷的有人期望基於 UDP 協議通訊來獲得更快的響應速度,而又想讓通訊流像 TCP 一般可靠。我也時常思考這個問題,到底該怎麼做這件事?
如果基於 UDP 可以做的比 TCP 更好,那麼一定是放棄了點 TCP 需要做到的東西。
一條路是寄希望於業務邏輯上允許資訊丟失:比如,在同步狀態中,如果狀態是有實效性的,那麼過期的狀態資訊就是可丟失的。這需要每次或週期性的全量狀態資訊同步,每個新的全量狀態資訊都可以取代舊的資訊。或者在同步玩家在場景中的位置時可以用這樣的策略。不過在實際操作中,我發現一旦允許中間狀態丟失,業務層將會特別難寫。真正可以全量同步狀態的場合也非常少。
那麼,不允許資訊丟失,但允許包亂序會不會改善? 一旦所有的包都一定能送達,即丟失的包會用某種機制重傳,那麼事實上你同樣也可以保證次序。只需要和 TCP 一樣在每個包中加個序號即可。唯一有優勢的地方是,即使中間有包晚到了,業務層有可能先拿到後面的包處理。
什麼情況下是包次序無關的呢?最常見的場合就是一問一答的請求迴應。採用這種方式的, UDP 在網際網路上最為廣泛的應用,就是 DNS 查詢了。
在網路狀況不好的時候,我們可以看到有時採用短連線反而能獲得比長連線更好的使用者體驗。不同的短連線互不影響,無所謂哪個迴應先到達。如果某個請求超時,可以立刻重新建立一條新的短連線重發請求。這時,丟包重發其實是放在業務層來做了。而一問一答式的小資料量通訊,正是 TCP 的弱項:正常的 TCP 連線建立就需要三次互動,確定通訊完畢還需要四次互動。如果你建立一次通訊只為了傳輸很少量的一整塊資料,那麼明顯是一種浪費。這也是為什麼 google 的 QUIC 對傳統的 http over TCP 有改善的空間。
我的思考結論就是:在 UDP 協議之上,實現一個帶超時的請求迴應機制,讓業務層負責超時重發,有可能取得比 TCP 通訊更好的效果。但其前提是:單個請求或迴應的包不應該過大,最好不要超過一個 MTU ,在網際網路上大約是 500 多位元組。