UE4中的OnlineSubsystem與Session
想弄清UE裡面的網路模組,始終繞不過OnlineSubsystem與Session這兩個概念,博主一開始對這一塊也挺頭疼的。後來花了點時間,參考網上的資料,對這一塊進行了一個相對全面的分析,希望對大家能有所幫助。
總的來說,博主暫時把UE網路分成三個部分:
第一個部分是網路同步,包括同步機制,RPC等。(參考 UE4網路同步詳解(一)——理解同步規則,UE4網路同步(二)——深入同步細節)
第二個部分是移動同步的模擬,這一塊在基本上都在CharacterMovement裡面。(參考 UE4移動元件詳解(二)——移動同步機制)
第三個部分是網路連線,包括OnlineSubsystem與Session等。也就是這篇部落格所要分析的內容。
一.OnlineSubsystem
在講解網路模式相關內容前,有必要先了解OnlineSubsystem與Session的相關知識。
1 什麼是OnlineSubsystem?
OnlineSubsystem是一個多網路平臺對接系統。其本身是抽象的,需要根據不同平臺完成具體的實現。你可以通過他對接Steam,GooglePlay,Amazon,Xbox等,進而使用對應平臺的各項自定義功能,如語音聊天,線上統計,遊戲大廳與遊戲匹配等。
總之,該系統的目的就是讓開發者可以快速的切換到不同平臺去釋出遊戲。需要注意的是,在UE裡面,OnlineSubsystem與Session機制息息相關,後面Session部分會做進一步的介紹。
2 OnlineSubsystem的初始化流程
首先有一點要宣告,4.8以前的的OnlineSubsystem是以一個Runtime模組整合在UE的原始碼裡面的。而我看到4.13以後(具體從哪個版本沒有確認)該系統改成了以外掛Plugin的形式整合在引擎裡面,所以Module的初始化不同版本可能會有差異。
在一開始執行引擎初始化的時候,FEngineLoop::PreInit函式會呼叫FEngineLoop::AppInit進行OnlineSubSystem的載入。(如果是以外掛形式整合,就要通過IPluginManager:: Get()->LoadMoudlesForEnabledPlugins()來完成了)
//FEngineLoop::AppInit LaunchEngineLoop.cpp
#if WITH_ENGINE
// Earliest place to init the online subsystems
// Code needs GConfigFile to be valid
// Must be after FThreadStats::StartThread();
// Must be before Render/RHI subsystem D3DCreate()
// For platform services that need D3D hooks like Steam
FModuleManager::Get().LoadModule(TEXT("OnlineSubsystem"));
FModuleManager::Get().LoadModule(TEXT("OnlineSubsystemUtils"));
// Init HighRes screenshot system.
GetHighResScreenshotConfig().Init();
#endif
當執行LoadModule的時候,會將當前模組新增到FModuleMap Modules;裡面並開始載入該模組,執行FOnlineSubsystemModule中的StartupModule函式。StartupModule函式決定當前使用哪個平臺,並載入相應平臺的模組並初始化,流程如下:
- 根據專案Project的Config目錄下的DefaultEngine.ini中配置確定當前使用哪個平臺,DefaultEngine.ini中的配置項如下,這裡使用的是steam
- 呼叫LoadSubsystemModule,根據配置讀取的字串去載入對應的OnlineSubsystem +Name的Module,這裡是OnlineSubsystemSteam
載入成功後還要繼續呼叫對應平臺Module的StartupModule函式。如果是steam,還需要到”../Engine/Binaries/ThirdParty/ Steamworks/Steamv132/Win64/”路徑下去載入其平臺的dll檔案(路徑可能有些偏差,具體看檔案steam_api64.dll的位置) 程式碼如下:
FString RootSteamPath = GetSteamModulePath();
FPlatformProcess::PushDllDirectory(*RootSteamPath);
SteamDLLHandle = FPlatformProcess::GetDllHandle(*(RootSteamPath + "steam_api64.dll "));上面對應平臺的Dll如果載入成功,需要註冊到基類FOnlineSubsystemModule裡面。其實就是新增到其OnlineFactories列表裡
- 前面完成了具體平臺的Onlinesubsystem模組的載入,但是其實真正的系統還沒有構建,只是建立並添加了其Factory而已。所以,繼續執行GetOnlineSubsystem嘗試獲取真正的OnlineSubsystem物件,如果沒有就通過Factory工廠進行建立
- 一般預設在非Shipping版本或者配置檔案OnlineSubsystemSteam的bEnable為false的情況下在初始化OnlinesubsystemSteam的時候(包括其他平臺),會CreateSubsystem失敗,然後Destroy該Onlinesubsystem。這樣引擎會預設建立OnlinesubsystemNull來替代
- 建立Onlinesubsystem成功且能正確獲取到後設置DefaultPlatformService為當前平臺
void FOnlineSubsystemModule::StartupModule()
{
FString InterfaceString;
// Load the platform defined "default" online services module
if (GConfig->GetString(TEXT("OnlineSubsystem"), TEXT("DefaultPlatformService"), InterfaceString, GEngineIni) &&
InterfaceString.Len() > 0)
{
FName InterfaceName = FName(*InterfaceString);
UE_LOG(LogOnline, Warning, TEXT("Begint to load default OnlineSubsystem module %s, using NULL interface"), *InterfaceString);
// A module loaded with its factory method set for creation and a default instance of the online subsystem is required 前面的步驟2到步驟5都在這裡執行
if (LoadSubsystemModule(InterfaceString).IsValid() &&
OnlineFactories.Contains(InterfaceName) &&
GetOnlineSubsystem(InterfaceName) != NULL)
{
DefaultPlatformService = InterfaceName;
}
else
{
UE_LOG(LogOnline, Warning, TEXT("Unable to load default OnlineSubsystem module %s, using NULL interface"), *InterfaceString);
InterfaceString = TEXT("Null");
InterfaceName = FName(*InterfaceString);
// A module loaded with its factory method set for creation and a default instance of the online subsystem is required
if (LoadSubsystemModule(InterfaceString).IsValid() &&
OnlineFactories.Contains(InterfaceName) &&
GetOnlineSubsystem(InterfaceName) != NULL)
{
DefaultPlatformService = InterfaceName;
}
}
}
else
{
UE_LOG(LogOnline, Warning, TEXT("No default platform service specified for OnlineSubsystem"));
}
}
關於OnlineSubsystemsteam的啟動:
如果Steam平臺的Onlinesubsystem可以初始化成功(FOnlineSubsystemSteam::Init),那麼該函式會針對伺服器和客戶端分別對Steam平臺進行初始化,即InitSteamworksServer以及InitSteamworksClient,另外還會進行官方伺服器列表的啟動更新操作。其中InitSteamworksClient函式除了在客戶端模式下進行Steam的初始化,還會根據Steam平臺的語言來設定遊戲客戶端的語言。(這裡是讀取配置檔案的Culture資訊,實際上要到本地化資料的位置去查詢如圖2-2)
另外,Steam模組還重寫了IPNetDriver以及NetConnection的部分介面,所以啟動steam後真正載入的是USteamNetDriver以及USteamNetConnection。(NetDriver通過CreateNamedNetDriver_Local建立,這裡會首先根據配置檔案DefaultEngine.ini裡面的內容嘗試載入配置的NetDriver,如果建立失敗就會建立預設的NetDriver)
3.3 OnlineSubsystem相關類關係
前面描述完流程後,可能角色還是有點暈,下面整理了Onlinesubsystem相關類的類圖。簡單總結一下,
- Onlinesubsystem系統屬於UE眾多Module(模組)的一個,所以需要通過一個管理類來管理所有Module的載入,這個管理類就是FMoudlemanager
- FModuleManager::Get().LoadModule(TEXT(“OnlineSubsystem”));這裡首先載入的模組是FOnlineSubsystemModule(各個OnlineSubsystemModule的基類),他會讀取配置檔案然後進一步載入指定的OnlineSubsystemModule(如FOnlineSubsystemSteamModule)
- FOnlineSubsystemModule有一個介面 GetOnlineSubsystem,這裡會根據對應Subsystem的FOnlineFactory(工廠模式)建立對應的FOnlineSubsystem
- 具體的OnlineSubsystem建立的成功表示他已經完成了相關的初始化Init(),不同的平臺的初始化內容各不相同,比如steam是針對客戶端與伺服器分別執行初始化的
- 不同的OnlineSubsystem擁有不同的OnlineSession,因為不同平臺對Session的處理有很大的差異,這個是由平臺來決定的
二.Session
1.什麼是Session?
其實session在WEB領域應用的更為廣泛,直譯為會話。廣義來講,Session可以理解為一種客戶端到伺服器保持連線的一種解決方案。狹義來說,Session是一種資料,用來記錄保持這個連線的相關內容。
進一步到UE裡面,我們可以更形象的理解為遊戲中的開房間。伺服器執行後,就好比一個玩家開了一個房間,然後等待其他玩家的加入。其他玩家可以在網路上(包括區域網,網際網路)搜尋到這個房間並加入進去。所以,把房間換成Session,就可以簡單理解為伺服器建立一個Session,然後客戶端搜尋並加入這個Session。整個Session相關的各種類與資料就構成了Session機制,用於管理客戶端與伺服器的連線。
那麼session是必須的麼?並不是,在4.14版本里面,整個OnlineSubsystem系統被作為一個外掛整合在引擎裡面,完全可以關掉,其相關的session功能也就基本上無效了。理論上如果伺服器不做任何限制,我們只要獲取到伺服器的IP與埠,我們客戶端就可以連線上去。
由此看來,Session最基本的功能就是:在一個多人遊戲中,客戶端需要通過Session機制連線到伺服器,以便伺服器驗證客戶端的合法性,控制連線人數等等。
2. UE4中的Session
UE中帶session的類確實不少,這可能給大家理解上帶來很多困難。你可以看到在遊戲初始化過程中有AGameSession,在OnlineSubsystem資料夾下有各種OnlineSessionXXX,我把最重要的幾個類拿出來畫了一個類圖。如下圖4-1:
總體來說,上面幾個類是Session機制的核心。
AGameSession顧名思義,其實其本身並不是Session資訊的產生者與擁有者,他主要的目的就是負責Gameplay遊戲邏輯與具體的底層Session機制的互動。比如遊戲裡面有一個在網路上尋找Session的功能,那麼一般我們會通過遊戲邏輯(比如UI按鈕事件)呼叫GameSession的查詢Session功能,GameSession會進一步從OnlineSubsystem裡面查詢Session。
那OnlineSubsystem與Session又是什麼關係?
我們可以認為,目前UE的機制裡面,Session資訊都是包含在OnlineSubsystem裡面的。因為不同的平臺有不同的驗證機制,所以除了基本的IP地址埠等資訊外,不同平臺對Session的處理還可能有不同的內容,這樣就出現了IOnlineSession介面以及對應平臺的如FOnlineSubsystemNull這樣的類,他把具體的Session資訊封裝,加入一些與當前OnlineSubsystem相關的操作與處理。
最後幾個類(FOnineSession,FNamedOnlineSession, FOnlineSesssionInfo),其實就是具體的Session資訊。裡面都是常見的Session資料,比如使用者唯一ID,伺服器IP地址,玩家數量配置等。前面不管是GameSession或者是OnlineSubsystem,最終操作的都是這裡的資料。
最後,還有需要注意的是區分客戶端與伺服器,Session的建立是在伺服器上進行的,玩家的註冊也是在伺服器上進行的(參考RegisterServer與RegisterPlayer),但是不代表客戶端沒有Session。客戶端也擁有圖中的各項Session資料,而且客戶端還可能通過搜尋找到多個Session(OnlineSubsystemNull有FNamedOnlineSession的陣列),每個Session表示不同的伺服器,這就是我們在網路上搜索Session並加入其中的基本原理。