|
文/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
- //对应上面流程第三步
- FString RootSteamPath = GetSteamModulePath();
- FPlatformProcess::PushDllDirectory(*RootSteamPath);
- SteamDLLHandle = FPlatformProcess::GetDllHandle(*(RootSteamPath + "steam_api64.dll "));
复制代码
- //OnlineSubsystemModule的启动代码
- 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信息,实际上要到本地化数据的位置去查找如图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并加入其中的基本原理。
|
|