1、概述
1.1、基於UDP的幀同步方案
在技術選型方面,之所以選擇幀同步方案,在Kevin的一篇介紹PVP幀同步后台實現的文章中已經做了詳細敘述,這里簡單摘要如下:
高一致性。如果每一幀的輸入都同步了,在同樣的上下文中,計算得出的結果應該也是同步的。
低流量消耗。除了幀同步,其它方案(比如狀態同步)想做到高一致性,需要同步非常大量的數據。無論是對於移動網絡,還是固絡都是不合適的。
服務器邏輯簡化。采用幀同步方案,服務器只需要做簡單的幀同步,不需要關心太多的業務細節。有利於客戶端功能的擴展和服務器的穩定和性能。
反作弊。客戶端只需要在適當機時上報校驗數據給服務器,服務器對2個客戶端上報的數據進行對比,就可以快速識別是否有人作弊。然后通過無收益的方式間接防止作弊。
那么,為什么選擇UDP而不是TCP呢?主要有2點原因:
弱網絡環境。
實時性要求。
我們通過一個測試APP,在WIFI和4G環境下,采用TCP和UDP兩種方式連接同一個服務器,分別獲得對應的RTT進行對比。
我們可以發現,在弱網絡環境下,UDP的RTT幾乎不受影響。而TCP的RTT波動比較大,特別是受丟包率影響比較明顯。
1.2、基於UDP的FSP協議棧
由於UDP具有不可靠性,所以在UDP的基礎上實現一個自定義的協議棧:FSP,即FrameSyncProtocol。
FSP的基本原理就是防照TCP的ACK/SEQ重傳機制,實現了傳輸的可靠性,同時還采用冗余換速度的方式,又保證了傳輸的**速率。在幀同步方案中一舉兩得。
2、技術原理
2.1、幀同步技術原理
如下圖所示,客戶端A的操作A1與客戶端B的操作B1封裝成OperateCmd數據發送給PVP服務器。PVP服務器每66MS產生一個邏輯幀,在該幀所在時間段內收到A1和B1后,生成一個Frame數據塊,在該幀時間結束時,將Frame發送給客戶端A和B。Frame數據塊內有該幀的幀號。客戶端A和B收到Frame數據后,便知道該幀內,客戶端A和B都做了什么操作。然后根據收到的操作A1和B1進行游戲表現,最終呈現給玩家A和B的結果是一致的。從而實現客戶端A與B的數據同步。
圖1 幀同步技術原理
2.2、FSP協議棧原理
如下圖所示,發送者維持一個發送隊列,對每一次發送進行編號。每一次發送時,會將待發送的數據寫入隊列。然后將隊列里的數據+編號發送給接收者。
接收者收到數據后,會將該編號回送給發送者以確認。發送者收到確認編號后,會將該編號對應的數據包從隊列中刪除,否則該數據仍保存在發送隊列中。
下次發送時,會有新的數據進入隊列。然后將隊列中的數據+最新的編號發送給接收者。以此循環反復。
圖2 FSP協議棧原理
上圖解析:
第1次發送,在發送隊列里只有Data1,於是將Data1和編號1(Seq=1)發送給接收者。收到確認編號1(Ack=1)后,將Data1從隊列中刪除。
第4到7次發送,由於從第4次發送開始就沒有收到確認編號,於是隊列中包含了Data4到Data7。第7次發送后,收到確認編號6,於是將Data4至Data6從隊列中刪除。
第8次發送,隊列中包含Data7和Data8。發送后收到確認編號8,從而將Data7和Data8從隊列中刪除。
以上的關鍵點是,發送者未收到確認編號,並不一直等待,而是會繼續下一次發送。結合圖1:
如果發送者是服務器,則會每隔66MS會將一個Frame數據寫入發送隊列,然后將該隊列里的所有Frame數據一起發送給客戶端 。
如果發送者是客戶端,則會在玩家有操作時,將玩家的每一個OperateCmd數據寫入發送隊列,然后將該隊列里的所有OperateCmd數據一起發送給服務器 。如果發送隊列不為空,則每隔99MS重復發送。如果發送隊列為空,則不再發送。直到玩家下一次操作。
由於服務器和客戶端即是發送者,又是接收者。則服務器和客戶端的每一次發送,除了會帶上該次發送的編號,還會帶上對對方發送編號的確認 。
3、技術實現
3.1、整體框架
圖3 PVP通訊模塊整體框架
這是一個典型的手游PVP通訊模塊的整體框架。這里主要分享一下FSP模塊和幀同步模塊的技術實現。
3.2、FSP模塊
FSP模塊主要用來實現FSP協議棧。其協議格式定義如下。
FSP上行協議定義:
Seq
Ack
SomeData
OperateCmd List
CheckSum
FSP下行協議定義:
Seq
Ack
Frame List
CheckSum
如下圖所示,是FSP模塊的接收邏輯流程。
圖4 FSP模塊接收邏輯流程
其中關鍵點是:
對Recv New Ack判斷,對曾經發送過的Operate進行確認刪除。
對Recv New Seq判斷,過濾掉因為網絡問題造成亂序的包。
上圖中,接收到的Frame最終都存儲在RecvQueue中。我們將接收邏輯放在子線程中。所以只需要在主線程中需要Recv的時刻從RecvQueue中讀取FremeList即可。
如下圖所示,是FSP模塊的發送邏輯流程。發送邏輯同樣放在子線程中。發送邏輯有2種觸發方式:
業務層主動調用發送
每隔指定時間觸發一次(在WIFI和4G下使用不同的時間,可以減少服務器收到的純確認包比例,有利於提高通訊性能)
圖5 FSP模塊主動發送邏輯流程
圖6 FSP模塊定時發送邏輯流程
3.3、幀同步模塊
下圖是幀同步模塊的實現框架。
圖7 幀同步模塊實現框架
按照上圖箭頭編號描述如下:
(1)負責接收來自FSP模塊的FrameList。
(2)將FrameList里的每1幀都存入FrameQueue。
(3)同時將FrameList的每1幀的幀號進行變換后,得到客戶端幀號。同時,在等下1個服務器幀到來之前,需要將客戶端的幀鎖定在下1個服務器幀的前一幀(LockFrameIndex)。然后 將FrameIndex和LockFrameIndex傳入FrameBuffer。
(4)客戶端每1幀從FrameBuffer中取出當前可能需要跳幀加速的倍數(SpeedUpTimes)。
(5)如果SpeedUpTimes為0,則表示正在緩沖中,沒有需要處理的幀。如果SpeedUpTimes是1,則表示緩沖結束,但是不需要加速,只需要處理最新的1幀。如果SpeedUpTimes大於1,則從FrameQueue里取出這SpeedUpTimes個幀, 將里面的SyncCmd取出來。
(6)將SyncCmd傳入OperationExecutor。
(7)OperationExecutor與具體游戲的業務邏輯相關聯,負責將SyncCmd傳入給業務邏輯和預表現模塊進行具體的處理。
其流程圖如下:
圖8 幀同步邏輯流程1
圖9 幀同步邏輯流程2
4、最新優化
4.1、斷線重連優化
在傳統網絡模塊開發思想中,當發送超時達到閥值,或者底層判定斷開連接時,需要重新建立連接。之前這部分工作是交給一個偏上層的模塊來執行,該模塊需要等Apollo通訊模塊連接成功之后,才進行PVP通訊模塊的連接。這樣使邏輯變得復雜。
由於UDP本身的不可靠性,可以認為網絡斷線也是其不可靠性的一部分。
而FSP協議棧就是為了解決UDP的不可靠性而設計的,所以也附帶解決了斷線重連問題。
去除了原來的斷線重連邏輯之后,用FSP模塊本身的特性來處理斷線重連,實測能夠提高網絡恢復的響應速度。由於PVP服務器設定的超時閥值是15秒,有些時候,其實網絡已經恢復,但是由於Apollo通訊模塊對網絡的恢復響應過於遲鈍,造成不必要的判輸。
4.2、接入GSDK
從目前接入GSDK后的數據來看,能夠減少一定的網絡延時,但是並不明顯。
4.3、AckOnly優化
AckOnly優化是指減少服務器收到的純確認包數據。這樣做的目的是:
減少包量,有助於在WIFI下節省路由器性能。GSDK有個統計表明,有大概20%多的網絡延時是因為路由器性能造成。
節省流量,一定程度上也可以節省網絡設備性能,同時在4G下為用戶省錢。
該優化分2部分實現:
(1)空幀免確認
(2)WIFI延遲確認
在優化前的AckOnly比例為:57%
空幀免確認優化后降到:38%
WIFI延遲確認優化后降到:25%
5、一些嘗試
將FSP模塊抽象得與業務無關,使之可快速完成一個使用幀同步方案通訊的Demo成為可能。
實驗了本地局域網PVP對局,只要在同一網段下,可以成功對局。(如果有需求,可以實現該功能)
實驗了本地藍牙PVP對局,發現藍牙是帶連接態的,並且其通訊是用類似TCP的數據流進行的。同時它與WIFI信號有干擾,如果開啟WIFI,其延時非常高。在非WIFI下,其單條數據的延時很低,但是如果以66MS的頻率發送數據,則延時又非常高。
建立了一套用於FSP在線診斷和斷線診斷的工具。
原文:http://gad.qq.com/article/detail/7171237
