遊戲伺服器:到底使用UDP還是TCP
在編寫網路遊戲的時候,到底使用UDP還是TCP的問題遲早都要面對。
一般來說你會聽到人們這樣說:「除非你正在寫一個動作類遊戲,否則你就用TCP吧」 或者是 「你能夠在MMO遊戲中用TCP,因為魔獸世界就用的TCP!」
遺憾的是,這些觀點都沒有反映這個問題的複雜性。
背景
首先,說明一下,我之前主要是用TCP進行網路編程。我曾為一個流行的在線紙牌遊戲編寫伺服器了好幾年,在高峰期我們的每台伺服器能夠承受4000到10000個連接(同一台物理機器上有多個伺服器進程在跑)都沒有問題。在我來看,TCP是一種安全而且常見的選擇。
儘管如此,我們最新的項目卻是使用UDP協議,而且我們的項目無法通過任何方式在TCP下工作。事實上,項目一開始使用的TCP,但是後來發現我們使用TCP無法達到我們需求的連接數量時,我們只能換成UDP了。
在使用中TCP表現怎麼樣呢
從原理上,TCP的優勢有:
任何一個和TCP打過交道的人都知道,要實現一個穩定的TCP網路連接,需要處理各種隱藏的坑,比如斷線檢測、慢速客戶端響應阻塞數據包,對開放連接的各種dos攻擊,阻塞和非阻塞IO模型等等。
除了上面列出的這些問題外,一個好的TCP模塊確實不好編碼實現。
但是,TCP最糟糕的特性是它對阻塞的控制。一般來說,TCP假定丟包是由於網路帶寬不夠造成的,所以發生這種情況的時候,TCP就會減少發包速度。
在3G或WiFi下,一個數據包丟失了,你希望的是立馬重發這個數據包,然而TCP的阻塞機制卻完全是採用相反的方式來處理!
而且沒有任何辦法能夠繞過這個機制,因為這是TCP協議構建的基礎。這就是為什麼在3G或者WiFi環境下,ping值能夠上升到1000多毫秒的原因。
為什麼不用UDP
UDP相對TCP來說既簡單又困難。
舉個例子來說,UDP是基於數據包構建,這意味著在某些方面需要你完全顛覆在TCP下的觀念。UDP只使用一個socket進行通信,不像TCP需要為每一個客戶端建立一個socket連接。這些都是UDP非常不錯的地方。
但是,大多數情況下你需要的僅僅是一些連接的概念罷了,一些基本的包序功能,以及所謂的連接可靠性。可惜的是,這些功能UDP都沒有辦法簡單的提供給你,而你使用TCP卻都可以免費得到。
這也是人們為什麼經常推薦TCP的原因。在用TCP的時候你可以不考慮這些問題,直到你需要同步連接的數量級達到500以上的時候。
所以,是的,UDP沒有提供所有的解決方法,但是就像你看到的那樣,這也正是UDP好用的地方。在某種意義上來說,TCP對UDP就好比是Hibernate和手寫SQL的區別。
使用TCP失敗的地方
人們經常給你建議,讓你去使用TCP,比如「TCP跟UDP一樣快」或者「遊戲X用TCP如此成功,所以TCP當然是首選」,然而,他們完全沒有理解為什麼在那個特定的遊戲中TCP是有效的,為什麼UDP不按照順序發送數據包呢?
那麼為什麼魔獸世界採用TCP呢?首先我們需要解釋這個問題。這個問題其實是「為什麼魔獸世界有的時候1000毫秒以上的延遲還能夠運行?」這是TCP的性質決定的,在發生丟包的時候,會產生巨大的延遲,因為TCP首先會去檢測哪些包發生了丟失,然後重發所有丟失的包,直到他們都被接收到。
可靠的UDP也是有延遲的,但是由於它是在UDP的基礎之上建立的通信協議,所以可以通過多種方式來減少延遲,不像TCP,所有的東西都要依賴於TCP協議本身而無法被更改。
就這一點來講,一些人要開始提到Nagle演算法了,實際上它是你在實現任意一個對延遲敏感的TCP模型時首先需要禁止使用的。
那麼魔獸世界以及其他的一些遊戲是怎麼處理延遲問題的呢?
方法也很簡單,他們能夠隱藏掉延遲帶來的影響。
在魔獸世界中,玩家和玩家是無法碰撞的:因為這類碰撞是無法通過一些預測來處理的,但是玩家和環境之間的碰撞卻是可以通過預測來處理的,所以這裡使用TCP是沒有問題的。
我們來看一下魔獸世界的戰鬥就會發現,玩家的攻擊指令發送給伺服器的操作是放在比如「attack_entity(entity_id)」或者」cast_spell(entity_id, spell_id)「的介面中來做的,換句話說,瞄準操作是獨立於進行的。如此一來,一些類似發起攻擊動作和釋放技能特效就能夠在沒有收到伺服器確認的情況下就直接執行,比如展現冰凍技能的效果就可以在伺服器沒有返回數據前在客戶端就做出來。
客戶端直接開始進行計算而不等待服務端確認是一種典型的隱藏延遲的技術。
幾年前,我為一個叫「Five Card Jazz」的紙牌遊戲編寫過客戶端。它使用的是http協議,它比直接的TCP協議連接的延遲更加嚴重。
我們用簡單的紙牌繪製和抽牌的動畫來掩蓋延遲的問題,所以延遲的問題只在非常糟糕的連接下才會被看出來。這種方法也非常的典型:發送請求的同時開始播放牌桌的動畫,一直播放翻動最後一張牌直到接收到了服務端傳回來的數據為止。魔獸世界的戰鬥特效就是使用類似的原理。
這也意味著,我們到底是使用TCP還是UDP取決於我們能否隱藏延遲。
TCP在什麼時候失效
一個採用TCP的遊戲必須能夠處理好突發的延遲問題(紙牌客戶端就很典型,對突發性的一秒的延遲,玩家也不會產生什麼抱怨)或者是擁有緩解延遲問題的好方法。
但是如果你運行的是一個無法使用任何減緩延遲措施的遊戲呢?玩家對玩家的動作類遊戲通常就屬於這個範疇,但是這也不僅僅限於動作類遊戲。
舉個例子:
我目前正在寫一個多人遊戲(War Arcana)。
一種常見的操作是,你快速的移動你的角色通過一張充滿戰爭迷霧的世界地圖,但是一旦你探索過,迷霧就會被打開。
為了確保遊戲的規則,防止玩家作弊,伺服器只能顯示玩家當前位置附近的信息。這意味著不像魔獸世界,玩家無法在沒有得到伺服器響應的情況下,做出完整的動作。和Five Card Jazz相比,我們即使允許500毫秒的延遲,也已經非常困難了。
在實現了遊戲的原型後,在區域網內一切都進行的非常順利,但當我們在WiFi環境下測試時,操作會間歇性的卡起來或者延遲高起來。寫了一些測試程序之後發現,WiFi環境下偶爾會發生丟包行為,每當發生丟包的時候,伺服器的響應速度就從100-150毫秒上升到1000-2000毫秒。
沒有任何辦法可以繞過TCP的這個設置來避開這個問題。
我們替換了TCP的代碼,用了自定義的可靠的UDP來實現,把大量的丟包產生的延遲降到了僅僅只有50毫秒,甚至比以前TCP不丟包的情況一個來回的延遲還要小。當然,這隻可能建立在UDP之上,這樣我們才對可靠性擁有完全的掌控力。
困惑:可靠的UDP只是TCP的一種簡單的實現?
你有沒有聽過這種說法:「可靠的UDP就像TCP一樣,所以還是用TCP吧」。
問題是這種說法是錯誤的。可靠的UDP一點也不像TCP,要去實現一個特殊的阻塞控制。事實上,這也是你使用可靠UDP代替TCP的最大的原因,避免TCP的阻塞控制。
另一個重點是可靠的UDP的可靠性是如何保證的。這裡有很多種方法去實現。我非常喜歡Quake3網路庫代碼里的一些想法,它們也激發了我在War Arcana中使用UDP協議。
你也可以使用許多支持可靠通信的UDP庫,當然,這樣在可靠性方面,相比自己手動實現全部的代碼而言,可能會更加通用而失去了一些性能優勢。
底線
那麼到底是用UDP還是TCP呢?
如果是由客戶端間歇性的發起無狀態的查詢,並且偶爾發生延遲是可以容忍,那麼使用HTTP/HTTPS吧。
如果客戶端和伺服器都可以獨立發包,但是偶爾發生延遲可以容忍(比如:在線的紙牌遊戲,許多MMO類的遊戲),那麼使用TCP長連接吧。
如果客戶端和伺服器都可以獨立發包,而且無法忍受延遲(比如:大多數的多人動作類遊戲,一些MMO類遊戲),那麼使用UDP吧。
這些也應該考慮在內:你的MMO客戶端也許首先使用HTTP去獲取上一次的更新內容,然後使用UDP跟遊戲伺服器進行連接。
永遠不要害怕去使用最佳的工具來解決問題。
推薦閱讀:
※英特爾? 至強? Xeon處理器 科普
※websocket多台伺服器之間怎麼共享websocketSession?
※SSR - 收藏集 - 掘金
※遊戲專用高防伺服器
※類QQ聊天程序設計-伺服器篇