《Exploring in UE4》Session与Onlinesubsystem[概念理解]

2018-05-11作者:Jerish
https://zhuanlan.zhihu.com/c_164452593


想弄清UE里面的网络模块,始终绕不过OnlineSubsystem与Session这两个概念,我一开始对这一块也挺头疼的。后来花了点时间,参考网上的资料,对这一块进行了一个相对全面的分析,希望对大家能有所帮助。

总的来说,我暂时把UE网络分成三个部分:

第一个部分是网络同步,包括Actor同步机制,RPC等。

第二个部分是移动同步的模拟,这一块在基本上都在CharacterMovement里面。

第三个部分是网络连接,包括OnlineSubsystem与Session等。也就是这篇文章所要分析的内容。

一.OnlineSubsystem

1.1 什么是OnlineSubsystem?

OnlineSubsystem是一个多网络平台对接系统。其本身是抽象的,需要根据不同平台完成具体的实现。你可以通过他对接Steam,GooglePlay,Amazon,Xbox等,进而使用对应平台的各项自定义功能,如语音聊天,在线统计,游戏大厅与游戏匹配等。

总之,该系统的目的就是让开发者可以快速的切换到不同平台去发布游戏。需要注意的是,在UE里面,OnlineSubsystem与Session机制息息相关,后面Session部分会做进一步的介绍。

1.2 OnlineSubsystem的初始化流程

首先有一点要声明,4.8以前的的OnlineSubsystem是以一个Runtime模块集成在UE的源码里面的。而我看到4.13以后(具体从哪个版本没有确认)该系统改成了以插件Plugin的形式集成在引擎里面,所以Module的初始化不同版本可能会有差异。

在一开始执行引擎初始化的时候,FEngineLoop:reInit函数会调用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中的配置项如下,图1-1使用的是steam

2.调用LoadSubsystemModule,根据配置读取的字符串去加载对应的OnlineSubsystem +Name的Module,这里是OnlineSubsystemSteam

3.加载成功后还要继续调用对应平台Module的StartupModule函数。如果是steam,还需要到”../Engine/Binaries/ThirdParty/ Steamworks/Steamv132/Win64/”路径下去加载其平台的dll文件(路径可能有些偏差,具体看文件steam_api64.dll的位置) 代码如下:

4.上面对应平台的Dll如果加载成功,需要注册到基类FOnlineSubsystemModule里面。其实就是添加到其OnlineFactories列表里

5.前面完成了具体平台的Onlinesubsystem模块的加载,但是其实真正的系统还没有构建,只是创建并添加了其Factory而已。所以,继续执行GetOnlineSubsystem尝试获取真正的OnlineSubsystem对象,如果没有就通过Factory工厂进行创建

6.一般默认在非Shipping版本或者配置文件OnlineSubsystemSteam的bEnable为false的情况下在初始化OnlinesubsystemSteam的时候(包括其他平台),会CreateSubsystem失败,然后Destroy该Onlinesubsystem。这样引擎会默认创建OnlinesubsystemNull来替代 ,配置见图1-2

7.创建Onlinesubsystem成功且能正确获取到后设置DefaultPlatformService为当前平台

图1-1

图1-2

  1. //对应上面流程第三步
  2. FString RootSteamPath = GetSteamModulePath();
  3. FPlatformProcess::PushDllDirectory(*RootSteamPath);
  4. SteamDLLHandle = FPlatformProcess::GetDllHandle(*(RootSteamPath + "steam_api64.dll "));
复制代码

  1. //OnlineSubsystemModule的启动代码
  2. void FOnlineSubsystemModule::StartupModule()
  3. {
  4.     FString InterfaceString;
  5.     // Load the platform defined "default" online services module
  6.     if (GConfig->GetString(TEXT("OnlineSubsystem"), TEXT("DefaultPlatformService"), InterfaceString, GEngineIni) &&
  7.         InterfaceString.Len() > 0)
  8.     {
  9.         FName InterfaceName = FName(*InterfaceString);
  10.         UE_LOG(LogOnline, Warning, TEXT("Begint to load default OnlineSubsystem module %s, using NULL interface"), *InterfaceString);
  11.         // A module loaded with its factory method set for creation and a default instance of the online subsystem is required  前面的步骤2到步骤5都在这里执行
  12.         if (LoadSubsystemModule(InterfaceString).IsValid() &&
  13.             OnlineFactories.Contains(InterfaceName) &&
  14.             GetOnlineSubsystem(InterfaceName) != NULL)
  15.         {
  16.             DefaultPlatformService = InterfaceName;
  17.         }
  18.         else
  19.         {
  20.             UE_LOG(LogOnline, Warning, TEXT("Unable to load default OnlineSubsystem module %s, using NULL interface"), *InterfaceString);
  21.             InterfaceString = TEXT("Null");
  22.             InterfaceName = FName(*InterfaceString);
  23.             // A module loaded with its factory method set for creation and a default instance of the online subsystem is required
  24.             if (LoadSubsystemModule(InterfaceString).IsValid() &&
  25.                 OnlineFactories.Contains(InterfaceName) &&
  26.                 GetOnlineSubsystem(InterfaceName) != NULL)
  27.             {
  28.                 DefaultPlatformService = InterfaceName;
  29.             }
  30.         }
  31.     }
  32.     else
  33.     {
  34.         UE_LOG(LogOnline, Warning, TEXT("No default platform service specified for OnlineSubsystem"));
  35.     }
  36. }
复制代码

关于OnlineSubsystemsteam的启动:

如果Steam平台的Onlinesubsystem可以初始化成功(FOnlineSubsystemSteam::Init),那么该函数会针对服务器和客户端分别对Steam平台进行初始化,即InitSteamworksServer以及InitSteamworksClient,另外还会进行官方服务器列表的启动更新操作。其中InitSteamworksClient函数除了在客户端模式下进行Steam的初始化,还会根据Steam平台的语言来设置游戏客户端的语言。(这里是读取配置文件的Culture信息,实际上要到本地化数据的位置去查找如图1-3)

另外,Steam模块还重写了IPNetDriver以及NetConnection的部分接口,所以启动steam后真正加载的是USteamNetDriver以及USteamNetConnection。(NetDriver通过CreateNamedNetDriver_Local创建,这里会首先根据配置文件DefaultEngine.ini里面的内容尝试加载配置的NetDriver,如果创建失败就会创建默认的NetDriver)

图1-3

1.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的处理有很大的差异,这个是由平台来决定的

图1-4

二.Session

2.1.什么是Session?

其实session在WEB领域应用的更为广泛,直译为会话。广义来讲,Session可以理解为一种客户端到服务器保持连接的一种解决方案。狭义来说,Session是一种数据,用来记录保持这个连接的相关内容。

进一步到UE里面,我们可以更形象的理解为游戏中的开房间。服务器运行后,就好比一个玩家开了一个房间,然后等待其他玩家的加入。其他玩家可以在网络上(包括局域网,互联网)搜索到这个房间并加入进去。所以,把房间换成Session,就可以简单理解为服务器创建一个Session,然后客户端搜索并加入这个Session。整个Session相关的各种类与数据就构成了Session机制,用于管理客户端与服务器的连接。

那么session是必须的么?并不是,在4.14版本里面,整个OnlineSubsystem系统被作为一个插件集成在引擎里面,完全可以关掉,其相关的session功能也就基本上无效了。理论上如果服务器不做任何限制,我们只要获取到服务器的IP与端口,我们客户端就可以连接上去。

由此看来,Session最基本的功能就是:在一个多人游戏中,客户端需要通过Session机制连接到服务器,以便服务器验证客户端的合法性,控制连接人数等等。

2.2. UE4中的Session

UE中带session的类确实不少,这可能给大家理解上带来很多困难。你可以看到在游戏初始化过程中有AGameSession,在OnlineSubsystem文件夹下有各种OnlineSessionXXX,我把最重要的几个类拿出来画了一个类图。如下图4-1:

图2-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并加入其中的基本原理。