1. 程式人生 > >動作手遊實時PVP技術揭密(伺服器篇)

動作手遊實時PVP技術揭密(伺服器篇)

前言

  我們的遊戲是一款以忍者格鬥為題材的ACT遊戲,其主打的玩法是PVE推圖及PVP 競技。在劇情模式中,高度還原劇情再次使不少玩家淚目。而競技場的樂趣,伴隨著賽季和各種賽事相繼而來,也深受玩家喜愛,從各直播平臺幾萬到幾十萬的觀眾可見一斑。然而,在移動端推出實時PK並不是一蹴而就的,本文將向大家介紹遊戲的實時PVP相關技術。

 

 

 

技術選型

  實時PK的表現方式,是將N個玩家的行為快速同步給其它玩家展示並保持一致性的過程。這裡面涉及到幾個要思考的要點:

  • 同步什麼?可以是玩傢俱體操作(如移動操作),也可以是某按鍵操作(如方向鍵),這兩者是有些微區別的。
  • 怎麼同步?可以選擇方式多種,傳統的C/S模式,或者是P2P形式,或者是幀同步等。
  • 同步方式?載體可以是TCP/UDP。使用哪個比較靠譜?

  基於以上的考量,在遊戲中,使用的是基於可靠UDP的幀同步模型作為實時PVP的技術方案。你問為什麼不採用TCP,為什麼不用C/S,為什麼不上P2P,下文分曉。

 

實現細則

  這裡講述一些重要細節,以解決眾多的Why not問題。

使用幀同步模型

  為什麼選擇幀同步,直接原因是繼承之前AI(機甲旋風)經驗,對於ACT型別遊戲,我們認為幀同步是不錯的方案,主要是能夠獲得以下好處:

  • 高一致性。對於格鬥中的技能連招,如果不精確到幀,會出現一些詭異現象。試想某個浮空下落的角色,可能一方客戶端看到已經倒地,另一方在未倒地時接上其它技能,會出現兩個結果,即使將其拉扯回,同樣奇怪。而幀同步的機制,保證了雙方客戶端的一致性。
  • 伺服器邏輯簡化。傳統的C/S 架構下,在伺服器計算及校驗,大量的核心邏輯需要客戶端及伺服器都實現一遍,使用幀同步可以大大簡化及保證伺服器的穩定性。
  • 低流量消耗。在行動網路中,使用者的流量即金錢,如果我們遊戲的核心部分耗流量嚴重,那讓45%1左右的非wifi使用者情何以堪呢?
  • 反作弊。講道理來說,幀同步對於反作弊並不友好,但是有一個簡單的做法可以快速反作弊,就是雙方資料不一致時(上報的校驗資料或者戰鬥結果),即檢測到有人作弊。

  那麼,幀同步的過程是如何進行的呢?下圖演示了兩個客戶端PlayerA/PlayerB和Server互動的過程。

 

 

  對於客戶端而言,不斷的上報其行為(action)至伺服器,並且等待伺服器下發的幀包驅動其邏輯。這種方式是幀鎖定同步(lockstep)的一種演化 2
對於伺服器來說,固定幀間隔(66ms)將佇列中的PlayerA/PlayerB的actions放在一個Frame中,並同步給兩個客戶端,這似乎和BucketSync類似。

  我常被問到一個問題,對方的卡頓會不會影響我的遊戲體驗,從以上我們的同步原理中,或許你可以找到答案。

 

使用UDP代替TCP

  幀同步並對協議層是TCP還是UDP並無要求,但我們打一開始就沒考慮TCP直接擁抱UDP,究其原因,若是對TCP的特性稍有了解,大概會背那三字經:“慢啟動”,“快重傳”,“擁塞避免”(三個字!)。我概括它以下幾點不太適合對實時性要求高,對延遲敏感的移動網路遊戲:

  • 慢啟動演算法不適合行動網路的情景,在行動網路環境下訊號時好時壞是常態,慢啟動會使資料不能及時快速達到對端。
  • 擁塞避免演算法不適合行動網路主要原因是其考慮到網路的公平性及收斂性,並且AIMD 演算法會使實時性大受影響,延遲明顯提升。

  還有TCP協議用於重傳的RTO的指數變化及擁塞演算法的實現Nagle的快取等,都是TCP並不太適合高實時性要求的遊戲玩法的原因,不再一一列舉。

  那麼為什麼UDP對比TCP更合適呢?多說無益,看一組資料:

 

 

  顯而易見,在各種網路情形下,UDP的表現(延遲分佈)基本上都優於TCP。測試程式在相同的網路環境下,通過設定一定的延遲,丟包率,抖動等,獲得TCP/UDP的RTT3 。

  對於P2P的選擇,我們放棄的原因是本身UDP打洞並非100%成功,而若打洞失敗則仍要走伺服器轉發,故簡化設計考慮,未選擇P2P方式去同步。

 

自己DIY可靠UDP

  上面講了用什麼(UDP)同步什麼(幀資料)的問題,有同學要問了,UDP不可靠傳輸,丟包怎麼辦?當然,為了資料一致,我們不允許(或許可以允許少量)丟包,TCP的可靠性是由ack/seq+重傳去保證的,世面上大多數的可靠UDP實現,也都是類似原理。

  在考察了幾個可靠UDP的實現(UDT,ENet等)覺得略顯複雜,並且在我們開發時,公司內部的可靠UDP實現未達到可使用階段,鑑於自己重新造個輪子並不複雜,於是挽起袖子寫了起來。

  用於可靠UDP的實現,其UDP協議自定義包頭是這樣的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//客戶端上行包

typedef struct UdpPkgRecvHead

{

    uint16_t seq; //start from 1

    uint16_t ack; //ack server seq

    uint16_t sid; //player uid

    uint8_t  action_len; //pkg actions contain

}UdpPkgRecvHead;

//...

//伺服器下行包

typedef struct UdpPkgSendHead

{

    uint16_t seq; //inc when frame id ++

    uint16_t ack; //ack client pkg's seq

    uint8_t frame_len; //pkg frames contain

}UdpPkgSendHead;

 

  基於以上的定義,可靠性保證的過程大概如圖如示:

 

  客戶端在滿足以下條件之一後,發包給伺服器:

  • 玩家有操作時
  • 發包後間隔(99ms)未收到回包時
  • 收到包一定間隔(99ms)後

  若玩家有操作,則確認資訊隨著玩家的操作上行包“捎帶”至伺服器 ; 如果無操作,則固定時間上報確認資訊給伺服器。客戶端的seq每一個操作行為(action)時加1,伺服器seq在每一幀資料下發時加1 ,並且雙方的RTO 取值不同4

  對於可靠性的保證,可以採用請求重傳,而我們使用的是冗餘重傳。使用冗餘重傳的一個好處是,簡化了麻煩的時序問題,並且收到的每個包都是完整的順序的。對於網路擁塞情況下的頻寬利用優於TCP,它不足之處是流量略微增加了些。下圖是冗餘重傳的過程:

 

  圖解如下:

  Client發Action1過來,記seq=1,伺服器未收到。
  Client又新增了Action2,此時新包將同時包含Action1,Action2,並且seq=2。
  Server確認了上一步驟的包,發給Client的包Ack=2表示確認。
  Client由於某些原因(可能延遲等)尚未收到Server的Ack=2的確認,此時新增Action3,併發包seq=3。
  Client再次發Action4時,發現之前已經Ack=2了,故新包將只帶Action3,Action4並且seq=4。

  這裡演示了冗餘傳輸的過程,伺服器對於收到的包,可以根據seq/ack情況動態去除冗餘或者丟棄過期包。可能你會覺得全冗餘是否不太合適並且有明顯優化空間?在實際現網長期執行中,全冗餘的冗餘率是100%左右,相比於一些可靠傳輸的重發最近三幀等方式,這種為可靠性付出的代價是合適的並且也提高了更多實時性。

  小結:在刨除一些優化及細節外,這就是可靠UDP的機制,簡單有效,開銷極小5。經測試及實際線上執行來看,在弱網路環境下的表現也是不錯的。

 

反作弊策略

  實現的技術細節告一段落,接下來談談我們的反作弊策略。有些經驗是實踐下的真知:)
我們知道幀同步的一切都在客戶端運算了,伺服器能做的顯得很有限。我們不知道玩家當前的位置,不知道玩家的技能使用情況,不知道玩家當前血量,拿什麼來反作弊?

實時的檢測,做了兩點:

  客戶端固定間隔上報雙方血量及技能使用情況,伺服器進行記錄
  單局結束,上報勝負關係

  基於這兩點,我們可知道某一幀玩家的血量是多少,每個人都上報自己的及對方(們)的,雙向校驗可看出有有無作弊行為。困擾而不得其解的是,當只有兩人時,判斷誰是作弊者比較麻煩。當兩人以上時,可以仲裁。

  我們做了一點容錯,當勝負結果異常時,才去進一步檢查上報的記錄以判斷作弊者,判輸。而對於上報資料並不一致但是勝負關係一致的情況,記錄日誌來診斷(容易發生在版本變更時)問題。

  通過實時的檢測,基本可以檢測到單局中作弊行為(加速,無限CD,鎖血等),因為他們最終都導致雙方資料不一致而結算不一致或上報血量不一致。

 

非實時的統計學反作弊方案

  當有一些漏洞可被利用但是一時無法定位的時候,統計學上的反作弊會比較有用。這裡說的漏洞是指通過某種行為使對方掉線或者不發包等未知原因導致遊戲異常結束的行為。

  我們在遊戲結算時,非正常獲勝的(掉線等)都會記錄下來,並且作用於一個懲罰機制。

  每天通過非正常獲勝的次數有上限,達到上限後,其非正常獲勝都將不計。
  非正常獲勝的次數作用於實時檢測邏輯,如果雙方資料不一致,非正常獲勝次數多的玩家失敗。
  非正常獲勝次數影響玩家進入匹配,次數越高需要等越久才能開始匹配。

  這個方案在線上發揮過作用,有效阻擋了少部分非正常玩家利用漏洞獲益,減少了其影響面。

 

後話

  上文介紹了遊戲的實時PVP的技術實現,這裡配一個架構圖,看看其外圍。

 

 

  有兩點需要說明:

    • TGW的多通接入支援UDP四層,UDP 服務需要監聽所有tunel的ip/port。
    • 幀同步的原理,要求我們必須精細化匹配。遊戲中是二進位制版本+資源版本一致才相互匹配,可以做到更精細化的根據出戰忍者雙方客戶端資料hash值是否一致進行匹配。