以下内容都是基于Unreal Engine版本:4.18.2-0+++UE4+Release-4.18。
操作系统是:Window10 x64专业版。
编译工具:VS2017 企业版。
承接上一篇文章UE4 ShooterGame Server研究最后遗留的内容,这篇文章主要是讲一讲对UE4控制服务器(后文简称ControlSvr)的思考实现。
为何需要ControlSvr?
用模块化的思想来看游戏,除了核心战斗之外,每个游戏都有自己独特模块:第三方的XX、外围成长、商业化运营/活动、存储相关……
UE4Svr给我们提供核心战斗模块(这个模块肯定不止一个进程、一台服务器),为了管理UE4Svr的服务器群,我们需要一个ControlSvr模块来管理这些UE4Svr。
ControlSvr要做哪些事情?
- 能与UE4Svr进行通信,记录UE4Svr提供游戏服务的IP/PORT;
- 全面管控UE4Svr的状态,至少需要实时更新空闲、忙碌、可能断线3种状态(这里解释一下可能断线这个状态,指在距上次收到心跳多久后没收到心跳);
- 分配空闲的UE4Svr给Client使用,生成Key用作Client连接合法性校验;
- 战斗结算的中转;
UE4Svr怎么配合?
解决Socket通信问题
自定义Socket网络通信实现在GameInstance中,可以C++原生实现或者嵌入库代码。
那么为什么将通信器放入GameInstance中,可以看看官方这篇文档里话:The GameInstance exists for the duration of the engine’s session, meaning that it is created when the engine starts up and not destroyed or replaced until the engine shuts down. A separate GameInstance exists on the server and on each client, and these instances do not communicate with each other. Because the GameInstance exists outside of the game session and is the only game structure that exists across level loads, it is a good place to store certain types of persistent data, such as lifetime player statistics (e.g. total number of games won), account information (e.g. locked/unlocked status of special items), or even a list of maps to rotate through in a competitive game like Unreal Tournament.(https://docs.unrealengine.com/en-US/Gameplay/Networking/Blueprints)。
增加所需协议
- HandShake,在UE4 GameInstance初始化完毕后与ControlSvr建立正式的TCP连接;
- HeartBeat,心跳;
- MatchBegin,由ControlSvr主动发送,告知比赛Key与PlayerUid,比赛准备开始;
- PlayerLeave,通知ControlSvr玩家中途退出;
- MatchEnd,比赛结束,通知ControlSvr结算;
- SvrIdle,UE4Svr进入空闲态;(这个是非必要的协议)
理解UE4Svr游戏状态变更
贴一段UE4 GameMode里面对于游戏状态定义的源码:
/** Possible state of the current match, where a match is all the gameplay that happens on a single map */ namespace MatchState { extern ENGINE_API const FName EnteringMap; // We are entering this map, actors are not yet ticking extern ENGINE_API const FName WaitingToStart; // Actors are ticking, but the match has not yet started extern ENGINE_API const FName InProgress; // Normal gameplay is occurring. Specific games will have their own state machine inside this state extern ENGINE_API const FName WaitingPostMatch; // Match has ended so we aren't accepting new players, but actors are still ticking extern ENGINE_API const FName LeavingMap; // We are transitioning out of the map to another location extern ENGINE_API const FName Aborted; // Match has failed due to network issues or other problems, cannot continue // If a game needs to add additional states, you may need to override HasMatchStarted and HasMatchEnded to deal with the new states // Do not add any states before WaitingToStart or after WaitingPostMatch }
结合我上一篇博文对ShooterGame状态分析,很容易就可以将游戏状态变更与所需发送的状态控制协议联系起来。
框架图
我们假设有一个匹配服务器(MatchSvr),先忽略里面的匹配策略与逻辑,那么大体的运作流程如下:
一些待解决的问题
ControlSvr单点问题
ControlSvr默认是一个有状态的服务器,将其直接使用成多点存在困难,目前思考的解决方案如下:
1、将ControlSvr改造成主备模式,一台主机,一台备机。主机失效,备机上升为主机,UE4Svr需要记录二台机器的IP,Socket失效后进行切换;
缺点是:造的轮子有点大,如何保证主备的状态同步一致性也是一些问题;
2、启用二台ControlSvr,UE4Svr根据IP/PORT进行Hash选择连接其中之一,分治的思想,UE4Svr需要记录二台机器的IP,Socket失效后进行切换;
缺点是:对UE4Svr管理分散了,可能ControlSvrA管理了一半Svr,ControlSvrB管理了另一半。对MatchSvr逻辑会加重(每次要询问2台ControlSvr);
3、利用Redis缓存将ControlSvr改造成无状态的,UE4Svr需要记录ControlSvr的IP列表;
缺点是:每次ControlSvr消息处理都要去缓存里面拉数据,会显著降低TPS,需要解决数据一致性的问题;
UE4Svr资源利用问题
现在对于单个UE4Svr进程默认只有有一个战斗房间,现在尚不清楚单个进程是否可以开启多个房间。
目前的做法是依靠同时起多个进程根据IP/PORT来区分不同的战斗房间,优点是:进程间资源相互隔离,缺点是:单个进程占用资源有点多。
(全文结束)
转载文章请注明出处:漫漫路 - lanindex.com