1. 程式人生 > >UE4中的OnlineSubsystem與Session

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函式決定當前使用哪個平臺,並載入相應平臺的模組並初始化,流程如下:

  1. 根據專案Project的Config目錄下的DefaultEngine.ini中配置確定當前使用哪個平臺,DefaultEngine.ini中的配置項如下,這裡使用的是steam
    這裡寫圖片描述
  2. 呼叫LoadSubsystemModule,根據配置讀取的字串去載入對應的OnlineSubsystem +Name的Module,這裡是OnlineSubsystemSteam
  3. 載入成功後還要繼續呼叫對應平臺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 "));

  4. 上面對應平臺的Dll如果載入成功,需要註冊到基類FOnlineSubsystemModule裡面。其實就是新增到其OnlineFactories列表裡

  5. 前面完成了具體平臺的Onlinesubsystem模組的載入,但是其實真正的系統還沒有構建,只是建立並添加了其Factory而已。所以,繼續執行GetOnlineSubsystem嘗試獲取真正的OnlineSubsystem物件,如果沒有就通過Factory工廠進行建立
  6. 一般預設在非Shipping版本或者配置檔案OnlineSubsystemSteam的bEnable為false的情況下在初始化OnlinesubsystemSteam的時候(包括其他平臺),會CreateSubsystem失敗,然後Destroy該Onlinesubsystem。這樣引擎會預設建立OnlinesubsystemNull來替代
    這裡寫圖片描述
  7. 建立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相關類的類圖。簡單總結一下,

  1. Onlinesubsystem系統屬於UE眾多Module(模組)的一個,所以需要通過一個管理類來管理所有Module的載入,這個管理類就是FMoudlemanager
  2. FModuleManager::Get().LoadModule(TEXT(“OnlineSubsystem”));這裡首先載入的模組是FOnlineSubsystemModule(各個OnlineSubsystemModule的基類),他會讀取配置檔案然後進一步載入指定的OnlineSubsystemModule(如FOnlineSubsystemSteamModule)
  3. FOnlineSubsystemModule有一個介面 GetOnlineSubsystem,這裡會根據對應Subsystem的FOnlineFactory(工廠模式)建立對應的FOnlineSubsystem
  4. 具體的OnlineSubsystem建立的成功表示他已經完成了相關的初始化Init(),不同的平臺的初始化內容各不相同,比如steam是針對客戶端與伺服器分別執行初始化的
  5. 不同的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並加入其中的基本原理。