前言
最近在做聯網戰鬥同步這塊的東西,讀了不少文章、書籍,于是整理了一下。
之前也有在 團隊内部技術分享 中分享過這塊内容,但是有些東西受限于時間,隻是大概的略過,重點放在了實作與遇到的難題解決上。
後來,在做優化調整的時候,又有不少新的收獲,改進了之前的分享稿。
歡迎各位小夥伴來一起讨論,通過分享讨論來不斷進步。
1. 簡介
現狀
網絡遊戲的同步方案,大概由以下三部分搭配組成
- 網絡傳輸協定
- 網絡同步模型
- 網絡拓撲結構
網絡傳輸協定(Network Transport Protocol)
- 分類
- UDP協定
- TCP協定
- 共同點
- 在 TCP/IP 協定族中[實體層,資料鍊路層,網絡層,傳輸層,應用層],位于 傳輸層的協定,均依賴底層 網絡層中的 IP協定。
- 差別
UDP TCP 傳輸可靠性 不可靠 可靠 傳輸速度 快 慢 帶寬 標頭小,省 標頭大,費 連接配接速度 快 慢 - 此外,TCP還提供了 流量控制、擁塞控制等
- 其他
- 關于 KCP協定
- KCP協定是一個快速可靠協定,它以浪費10%-20%帶寬的代價(相較于TCP協定),換取平均延遲降低 30%-40%,且最大延遲降低三倍的傳輸效果。純算法實作,并不負責底層協定的收發。
- 更詳細資訊可跳轉最下方 參考資料2
- 關于 KCP協定
網絡同步模型(Network Model)
- 分類
- 狀态同步(State Synchronization )
- 本質:上傳包含 遊戲外部變化原因集合(玩家操作等)及 中間狀态的子集(用戶端計算的部分);下發包含 遊戲狀态的集合。
- 幀同步(Lockstep)
- 本質:上傳下發隻包含遊戲外部變化原因集合(玩家操作等)。
- 對于A用戶端: [輸入A] -> [過程A + 運算A] -> 輸出A
- 對于B用戶端: [輸入B] -> [過程B + 運算B] -> 輸出B
- 確定 [輸入] 相同,再保證 [過程] 與 [運算] 一緻,那麼 [輸出] 一定是一緻的
- 邏輯幀,遊戲在邏輯層面是離散的過程,即可認為是一個邏輯幀一個邏輯幀進行邏輯運算。
- 渲染幀,遊戲在渲染層面是離散的過程,即可認為是一個渲染幀一個渲染幀進行畫面呈現。
- 遊戲邏輯幀 與 渲染幀 需要互相獨立。
- 本質:上傳下發隻包含遊戲外部變化原因集合(玩家操作等)。
- 狀态同步(State Synchronization )
- 共同
- 兩種同步方案都分為 上傳 和 下發 過程。
- 上傳 指用戶端将資訊傳輸給 伺服器/用戶端
- 下發 指用戶端從 伺服器/用戶端 中擷取資訊
- 兩種同步方案都分為 上傳 和 下發 過程。
- 對比
幀同步 狀态同步 流量 通常較低,取決于玩家數量 通常較高,取決于該用戶端可觀察到的網絡實體數量 預表現 難,用戶端本地計算,進行復原等 較易,用戶端進行預表現,伺服器進行權威演算,用戶端最終和伺服器下發的狀态進行調節或復原 确定性 嚴格确定性 不嚴格确定性 弱網影響 大,較難做到預表現 小,較易做到預表現 斷線重連 難,需要擷取所有相關幀且快播追上進度 易,根據快照迅速恢複 實時回放 難,用戶端需要消耗非常大性能去從頭播放到對應序列,回放完後需要快播追趕 易,根據快照進行回放,回放完再根據快照恢複 邏輯性能優化 難,用戶端需要運算所有邏輯,跟用戶端性能強相關 易,大部分邏輯可在伺服器進行,分擔用戶端運算壓力 外挂影響 大,用戶端擁有所有資訊,透視類外挂影響嚴重 小,伺服器可做視野剔除等處理 開發特征 平時開發高效,不需要前後端聯調;但是開發時要保證各子產品确定性,不同步BUG出現,難以排查 平時開發效率一般,需要前後端聯調,無不同步BUG 第三方庫影響 大,第三方庫需要確定确定性 小,第三方庫不需要確定确定性
網絡拓撲結構(Network Topology)
- 分類
- 對等結構
- P2P結構(Peer to Peer)
- 主從結構
- CS結構(Client-Server)
- 對等結構
- 共同
- 網絡時延的評估标準
- Ping
- 概念:網絡連接配接的兩個端之間的信号在網絡傳輸所花費的時間。
- 例:從A端發出信号開始計時,到B端響應并立刻傳回響應信号,A端收到響應後停止計時,該時長為Ping。
- RTT(Round Trip Time)
- 概念:一般可認為等于Ping,但此處 RTT = Ping + 兩個端的處理信号前等待時間 + 兩個端處理信号的時間,即 實際體驗到的遊戲時延。
- 例:A端邏輯發出信号開始計時,在A端等待一段時間、處理一段時間;發出到B端,在B端等待一段時間、處理一段時間;處理發出響應信号;再次在B端等待一段時間、處理一段時間;發出到A端,再次等待一段時間、處理一段時間;A端邏輯收到響應信号,停止計時;該時長為RTT。
- 标準(機關 ms)
- 極好:<= 20
- 優秀:21 ~ 50
- 正常:51 ~ 100
- 差:101 ~ 200
- 極差:>= 201
- Ping
- 丢包率
- 原因: 直接原因是由于 無線網絡 和 擁塞控制,根本原因比較複雜。
- 标準:
- 優秀:<= 2%
- 一般:2% ~ 10%
- 差:>= 10%
- 網絡時延的評估标準
- 對比
P2P結構 CS結構 樣式 全連接配接的網狀結構 星狀結構 連接配接數 O(n^2) O(n) 流量 各用戶端相等,均為 O(n^2) 伺服器為 O(n),用戶端為 O(1) 用戶端間的時延 較小,為RTT/2 較大,為RTT
2. 實作
廣義
廣義上來說,遊戲采用的技術是:
- 網絡傳輸協定: KCP & TCP
- 網絡同步模型: 幀同步
- 網絡拓撲結構: CS結構
圖例
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL5VEVONTUU10MFRkT0gnMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLwcDOyQzM0IjM0ITOwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
狹義
關聯類
- 同步管理類(SyncManager)
- 聯網戰鬥資料緩存類(CacheNetworkedFight)
- 聯網戰鬥場景類(NetworkFightScene)
同步管理類(SyncManager)
功能
- 同步幀的操作的處理
- 添加
- 處理
- 執行
- 修正
- 對玩家操作的處理
- 收集
- 上傳
- 吞噬
聯網戰鬥資料緩存類(CacheNetworkedFight)
功能
- 提供聯網戰鬥通用資料模型
- 解析玩家資料
- 用戶端與戰鬥服互動的中樞
- 發往戰鬥服消息,均由此統一發送
- 收到戰鬥服的消息,處理後轉發給其他業務類
- 斷線重連相關處理
- 追幀相關
聯網戰鬥場景類(NetworkFightScene)
功能
- 處理聯網戰鬥基礎場景流程、方法
- 房間狀态切換流程
- 建立角色資料及實體
- 傳送邏輯
- 實作本地戰鬥與聯網戰鬥切換
如何支援聯網戰鬥
- Cache,定義自己的Cache
- 通過 聯網戰鬥資料緩存類 解析資料
- 在 聯網戰鬥資料緩存類中 綁定戰鬥類型及Cache
- 重寫需要處理的協定收發
- Scene
- 繼承 聯網戰鬥場景類
- 處理資料 及 處理相應配置
- 是否自動傳送
- 是否本地戰鬥
為什麼采用幀同步
- 遊戲的核心邏輯在用戶端實作,伺服器隻負責轉發驗證等
- 遊戲類型及形式,動作類、房間為機關;更适合用幀同步
- 開發速度快,周期短
3. 重點處理
如同幀同步的簡介中介紹,要保證 輸出的一緻性,先要確定輸入、過程、運算的一緻性。
浮點數與定點數 [運算一緻性]
浮點數的運算在不同的作業系統,甚至不同的機器上算出來的結果都是有精度差異的。
一般解決該類問題方法:
- 使用定點數
- 使用分數
這裡主要麻煩點在于lua支援定點數,lua中的小數是double,需要把lua源碼中的基礎小數全部替換為定點數。
然後,實體引擎的計算,第三方庫的引用(比如随機數),都需要使用定點數。
确定的 随機數機制 [運算一緻性]
确定的随機數機制就是保證各個用戶端一旦用到随機數,随機出來的值必須是一樣的。
得益于計算機的僞随機,通過設定同樣的随機種子即可實作。
但是,在用戶端内,需要明确區分随機數的類型
- 戰鬥類
- 設計戰鬥的實體、BUFF、技能 等等
- 非戰鬥類
- 主要是顯示項的随機,比如 loading期間的 tip選擇
這裡,為了更明确區分,在用戶端做了一層封裝:
- AEUtil:GRandom,戰鬥類的随機數方法
- AEUtil:UIRandom,非戰鬥類的随機數方法
做好區分,也便于相關日志的列印。
使用戰鬥類随機數子產品:
- AI
- 行為樹
- 相機
- 技能、BUFF、特殊能力
- 實體相關
- 地圖相關
使用 非戰鬥類 随機數子產品:
- UI界面
- 外挂檢測
- 資料收集
- 音樂音效
當然,也不是絕對的,比如實體相關的有些可以不用戰鬥類随機數,比如NPC彈出個對話,也是純顯示性的。這裡是為了好區分,友善開發,一刀切了。
确定的 容器及算法 [過程一緻性]
- 對于lua語言,不要用 pairs 方式周遊,要用 ipairs,也相應就要求容器必須是數組
- 所有用到的算法,必須是 穩定 的算法
隔離與封裝 邏輯層 [過程一緻性]
所有子產品都可以分為 draw 與 update 兩部分
- draw 進行繪制,走本地繪制幀更新
- update 進行邏輯計算,走邏輯幀更新,可被幀同步接管
實作幀同步尤其需要對 邏輯層的資料進行封裝與隔離
以位移元件為例:
- 位移元件有兩套坐标
- 邏輯坐标
- 渲染坐标
- 人物的行走都是通過邏輯坐标計算,渲染坐标是在渲染幀的時候,将目前渲染坐标與邏輯坐标進行比較,再用內插補點平滑過渡
同理的還有:
- 碰撞框的計算
- 各元件
- 各實體
做好分離,也便于之後做快照相關的優化。
支援本地戰鬥
建立聯網戰鬥場景基類繼承自單人戰鬥場景基類,用來統一控制聯網相關的特殊操作,如 傳送,協定互動 等。
然後,設定本地戰鬥變量,用來進行控制,若是本地戰鬥,交由基類處理。
同步模式 及 處理幀政策 [過程一緻性]
同步模式
- 伺服器: 固定推幀 30幀/秒
- 用戶端:
- 30幀/秒,每次執行一次處理幀
- 60幀/秒,每次執行一次處理幀
- 30幀/秒,每次執行一次處理幀,繪制幀到來時,若有幀積壓,再執行一次處理幀
處理幀政策
每次執行一次處理幀操作,具體釋放幀數量
- 釋放1幀
- 逐漸釋放幀
- 累計幀數 < 2幀,釋放1幀
- 累計幀數 < 5幀,釋放2幀
- 累計幀數 < 10幀,釋放3幀
- 累計幀數 >= 10幀,釋放所有幀
- 可變釋放幀
- 釋放幀數量由 PlayFrameScale 變量控制,可 加速/減速 播放(一般用于處理回放)
- 釋放全部幀
斷線重連
斷線重連,主要由 聯網戰鬥資料緩存類(CacheNetworkedFight)負責。
- 從伺服器中擷取重連過程中的戰鬥幀
- 進入 追幀模式進行追幀,在追幀模式中,伺服器發來的推送幀會被緩存起來
- 追幀完畢後,退出追幀模式;并将追幀期間的 伺服器推送幀壓入 同步管理器中
同步校驗
驗證多個用戶端是否同步,主要依賴于随機數及調用随機數的位置。
在聯網戰鬥運作時,會将使用的随機數都列印出來,由于我們随機種子一緻,所調用的随機數序列也應該是一緻的,輔助以調用随機數的位置資訊,戰鬥結束後對不同用戶端的随機種子檔案日志比對,可以校驗同步。
我處理這塊的方式是使用兩個日志檔案,
- 一個用來做同步校驗:大部分内容是 使用随機數的子產品 + 随機數範圍 + 最終生成的随機數,還有一些必然一緻的過程日志。
- 另一個用來做同步排查:包含更詳細的日志資訊
兩場戰鬥結束後,用對比工具比較日志,一旦有差異,用更詳細的日志資訊,進行排查。
4. 優化項
聯網戰鬥同步向來不是一個做完就行的東西,而且也沒有一套東西,在各個類型遊戲通吃的情況。
是以,在實作完基礎的同步架構後,還有很長的路要走。
目前隻是搭建了一個基礎的架構,要真正投入還有下面這些優化項可以做。
下面這些東西,有些已經做了,有些正在做,有些是一些設想,即将做的;歡迎各位夥伴一起來讨論。
快照的支援
在幀同步基礎上,進行優化;就是 幀同步+快照 的模式。
其實已經不屬于幀同步了,偏向狀态同步。
快照作用就是将整個現場備份,缺點是資料量過大。
但是,我們以房間為機關的戰鬥,尤其适合 幀同步+快照;因為有明确的劃分機關;并且房間初始,很多東西都是不需要存儲的。
- 房間内的快照,所有實體的狀态(怪物、NPC、傳送門 等等),HP、EP、受損狀态 等等
- 房間間的快照,實體都是初始建立,且實體的建立是不通過幀的,可以本地處理
這三者差別,
- 幀同步 => 沒有進度條的播放器;想要看到第6分30秒的内容,必須從頭開始看
- 狀态同步 => 有進度條的播放器;知道時間,就可以直接切到相應時間開始播放
- 幀同步+快照 => 有進度條,但機關是5分鐘;要看 6分30秒的内容,不需要從頭看,但是也要從第5分鐘開始播放,直到6分30秒
安全性
幀同步的安全性也是一個重大的問題,可以分為幾大部分。
- 用戶端的安全子產品,遊戲的核心戰鬥邏輯演算都在用戶端進行,是以對于資料的加密,防篡改等都是由安全子產品統一處理。
- 網絡子產品,對于網絡層的外挂,由底層網絡子產品的加密等處理。
-
聯網戰鬥系統的防外挂子產品
基礎的幾個方案
- 每隔一段時間,進行玩家資訊收集并上傳(如血量、技能使用、buff使用),出現結算不一緻,由伺服器裁決,可以解決部分外挂
- 伺服器新開一個“用戶端”,在那個用戶端上跑所有的幀,作為評判依據。
- 等等
防外挂這個東西,就是魔高一尺,道高一丈,不斷優化,不斷調整的過程,有些東西也不好講太細,隻能說個大概。
不同步的處理
解決不同步問題,也是幀同步方案的一大痛點。
對于不同步的處理,可以分為三個部分:發現 -> 重制 -> 解決
作為開發,應該深有感觸,如果友善重制,那解決問題就很簡單了。
下面的處理方式都是針對傳統的不同步處理各個步驟,進行優化設想。
一般出現不同步: 發現不同步 -> 打開日志開關 -> 使用同樣的資料源 -> 複現問題 -> 解決問題
發現
發現不同步,最簡單粗暴的方式,肯定是人力跑,沒有技術成本,純跑…
但是,缺點很多:
- 人力不足
- 時間不足
- 不夠全面
- 不友善收集日志
- 不能展現技術實力
- 等等等等
是以,需要一種自動化的測試工具,來進行大量全面的測試。
目前打算是使用 python + jenkins 來部署自動化測試流水線,等測試完,再單獨來說一說。
重制
重制不同步,也是很重要的一個步驟,能完美重制,那距離解決就不遠了。
這裡預期采用的方案是,固定資料源 + 回放機制。
-
固定資料源
需要和伺服器配合,伺服器需要存儲參戰玩家資訊及幀内容,便于回放。
前期可以全部存儲,但是這樣伺服器壓力會比較大;後期可以将本地戰鬥産生的同步檔案形成MD5,發給伺服器;伺服器判斷各用戶端MD5不同,采取緩存錄像。
-
回放機制
需要用戶端實作一套根據幀内容回放機制,理論上來講幀同步的回放還是比較好實作的。
畢竟 确定的輸入,确定的運算,确定的過程,都與時間無關聯,可以得到确定的輸出。
但是,我們需要的是日志檔案,是以繪制幀内容可以忽略掉,盡量做到邏輯幀的播放,這樣在時間上也會更快。
解決
解決不同步問題,那就相對簡單很多了。
實作了上面的發現 與 重制,可以無數次反複執行不同步資料源,驗證是否解決也很便捷。
運作過程中的日志收集
這應該屬于發現不同步的部分。
在實際項目中,日志的實作都是比較粗暴的,一般來說線上運作的子產品,都不會開啟日志檔案。因為一般日志檔案都會比較大,尤其是查同步問題的日志檔案,涉及子產品繁多,産生檔案體積大。
是以,線上出不同步問題,往往也很難複現并解決,就是無法固定資料源。(不産生校驗檔案,就不能上傳MD5,不能傳MD5,伺服器無法判斷是否不同步,就不會緩存)
如果有一套性能損耗小一些的日志收集系統,會對同步問題的解決有很大的幫助,
正好最近看到了 《騰訊遊戲開發精粹》- 第六部分 - 第14章 - 一種高效的幀同步全過程日志輸出方案 。
上面的方案也對我有一些啟發,之後可以去實驗一下。
延遲處理
在實際測驗中,會有玩家回報卡頓情況。
延遲、卡頓的玩家體驗,一般可以分為:
- 延遲高
- 波動大
而且,不同遊戲類型對延遲的敏感度也不同,現在實作的這種偏格鬥類型的遊戲,對延遲敏感度還是比較高的。
再者,傳統幀同步的處理,邏輯上就是比本地操作要慢一幀:
A幀操作 -> B幀上傳 -> C幀執行
B ≥ A,C ≥ B+1
最終,還是要用資料來驗證延遲的具體位置,可以按照下流程打時間戳,再收集各個資料,來分析并解決延遲與卡頓:
這裡列出幾個方向:
- 玩家的位置
- 玩家的機型
- 戰鬥的時間
- 玩家的營運商
資料收集的選項:
- 最小值
- 最大值
- 平均值
- 波動值
這裡還要注意設定門檻值,防止某個異常操作,導緻資料不準确,拉高或拉低平均值。
甚至可以設定一些字段,來做篩選剔除異常資料。
5. 感悟
不信推送,相信幀
推送缺點
- 每個用戶端處理的時機不同
- 收到的時機:伺服器 推送A,再推送B;對于用戶端肯定是先收到A,再收到B
- 處理的時機:由于各用戶端阻塞狀态等,每個用戶端處理推送時機是不一緻的
- 推送可能丢失
- 推送内容,不支援在回放中處理
比如:
- 玩家複活
- 玩家掉線,删除角色
邏輯幀的更新流程是确定的
遊戲進行過程中,所有的相關子產品:
- 實體管理器 - EntityManager
- 場景管理器 - SceneManager
- 碰撞管理器 - AECollision
- 錄影機管理器 - CameraManager
- 等等
這些子產品的更新,都是固定順序執行,所有參數都是确定的,所用随機都是指定随機方法
需要用戶端同步的東西,必須通過幀來驅動。
同步的實質
同步,就像一個管理器,它的政策項設計項不難,難點主要在于管理的各個子產品的内部實作。因為一場戰鬥涉及的子產品很多,隻要有一個子產品實作有不同步的地方,整場戰鬥就不同步了。
在到後期查找不同步原因,也往往是去排查下屬子產品的實作,可能就是在于周遊方式,随機數的使用,邏輯幀繪制幀等。
主要還是要求:
- 實作同步下屬子產品的責任人,有聯網戰鬥的意識,盡量的不使用本地資料,能區分出哪些代碼可以使用繪制幀的更新,哪些堅決不允許使用繪制幀的更新。
- 做同步的責任人,對各個下屬子產品的涉獵廣泛,不能隻做完同步就可以了,還需要輔助下屬子產品進行不同步的排查。
解決這個問題的方向:
- 讓所有實作子產品的人都有聯網戰鬥的意識,對整個邏輯幀繪制幀等更新都有概念。(難度很大)
- 實作自動化同步測試,在發現不同步問題,輔助去定位解決。
參考資料:
- DonaldW-網絡遊戲同步技術概述
- KCP - A Fast and Reliable ARQ Protocol、
- 《騰訊遊戲開發精粹》