前言
- UE4版本4.25
孤傲雕:UE4 网络相关之网络驱动器(UNetDriver)的顺藤摸瓜
接前文, 在扒了一大堆代码之后, 对网络连接最开始的地方, 有了一定的了解.
- 服务器什么时候创建UNetDriver开始监听端口, 支持客户端连接.
- 客户端什么时候创建UPendingNetGame开始连接服务器.
下一步, 自然是进一步深扒, 客户端是如何和服务器建立连接, 直到两边可以发送RPC事件和进行属性同步为止.
客户端开始向服务器进行请求, 尝试创建连接, 主要处理在UPendingNetGame类中
- 创建UPendingNetGame, 通过NewObject创建对应对象, 构造函数无处理, 略
2. 调用UPendingNetGame::Initialize初始化函数, 传入FURL信息
这个信息包含了服务器地址、端口、地图信息、连接参数等内容.
3. 调用UPendingNetGame::InitNetDriver, 开始初始化网络驱动
先看上图这个全局变量GDisallowNetworkTravel, 如果为true, 则禁止任何网络连接切换, 在处理切换关卡时拒绝这一请求.
此处客户端与服务器的连接处理与该值对应, 如果不处理, 直接连接错误, 记录日志
然后, 扫一眼, 明显分为创建UNetDriver网络驱动和初始化两部分
创建初始化UNetDriver相关, 此处不做扩展, 见其他文章
如图广播FNetDelegates::OnPendingNetGameConnectionCreated
如果NetDriver中的ServerConnection的PacketHander存在, 会先调用PacketHandler的BeginHandshaking建立连接, 完成后调用SendInitialJoin.
不存在, 则直接创建.
在做了这么多准备工作(见其他文章)后.
这个时候, 和服务器一个沟通的渠道就建立完成了, 接下来就开始和服务器友好的"沟通"一下.
第一回合 NMT_Hello
客户端先向服务器友好礼貌打一声招呼!
Hello!
服务器收到这一NMT_Hello消息
从UWorld::Tick函数的多播转交到UNetDriver
从UNetDriver再转交给与客户端对应的UNetConnection, 并进一步处理
最后, 又反手丢回给UWorld::NotifyControlMessage
ServerConnection, 和服务器的连接, 存在自然就是客户端.
嗯, 那if里面是客户端逻辑, else里面就是服务器逻辑.
废话不多说, 否管哪来的, 先检查!
不合格? 简单, GoodBye! 走你!
嗯, 检测合格了,.?
好孩子, 来, 再让我看看, 有没有先和我问个好, 打个招呼.
好, 看起来像个样子.
孤傲雕:UE4 网络相关之网络版本获取与比对
但我们这可不是什么随随便便的地方, 打招呼的礼仪对不对? 是现在最流行的, 最高贵最优雅的那一种吗?
不是???
???
...
滚回去学, 学完了再说...
然后客户端就收到了服务器的NMT_Upgrade消息
从UEngine::TickWorldTravel转发到UPendingNetGame::Tick再
从UEngine::TickWorldTravel函数的转交到UPendingNetGame::Tick
从UPendingNetGame再转交给对应的UNetConnection, 并进一步处理
最后, 又反手丢回给UPendingNetGame::NotifyControlMessage
然后, 客户端就多播UEngine::BroadcastNetworkFailure, 类型为ENetworkFailure::OutdatedClient, 并记录对应错误日志
然后苦兮兮的更新版本去了.
第二回合 NMT_Hello
客户端好好的学了学最新的礼仪(更新到最新的客户端版本), 再一次向服务器友好礼貌打一声招呼!
Hello!
服务器再次看到了客户端.
嗯, 这次礼仪对了, 口令!
没有口令? ...
嗯, 对就是没有口令!
// 如果有口令, 会对口令进行验证, 并考虑是否传输进行加密, 见其他文章
通过FPlatformTime::Cycles获得一个数字, 取WinAPI QueryPerformanceCounter得到高精度计时器的值64位, 取其低32位
// server sends client challenge string to verify integrity
DEFINE_CONTROL_CHANNEL_MESSAGE(Challenge, 3, FString);
当网络状态有效, 就向客户端发送NMT_Challenge信息
同时调用SetExpectedClientLoginMsgType, 参数NMT_Login, 招呼打完了, 该干正事了 : 登录.
第三回合 NMT_Login
客户端又收到了服务器的NMT_Challenge消息
兜兜转转又回到了UPendingNetGame::NotifyControlMessage处理
然后PartialURL数据, Host清空, Port取默认值
嗯, 与服务器连接已经建立了, 这两个值没用处了
然后如图
当ULocalPlayer::GetNickname有值时候, 添加一个Name参数
当ULocalPlayer::GetGameLoginOptions有值时, 添加对应参数
然后设置UConnection的PlayerID, 通过ULocalPlayer::GetPreferredUniqueNetId获得
例如 : L"DESKTOP-10Q6V1I-FCDB05414DB72C66F61B6E80E315FEA9"
然后获得OnlinePlatformName, 平台名称
通过UGameInstance::GetOnlinePlatformName, 然后找UOnlineEngineInterface::GetDefaultOnlineSubsystemName获得
UOnlineEngineInterface是个接口类, 自然不同平台有对应的子类, 有不同的实现
其中, UOnlineEngineInterfaceImpl是一个常用实现, 从IOnlineSubsystem中获得对应的平台名称
同理又得, FOnlineSubsystemImpl又是IOnlineSubsystem的一个常用实现, SubsystemName该属性就是平台名称
以FOnlineSubsystemSteam为例, 它将STEAM_SUBSYSTEM作为SubsystemName传入, 最后平台名称, 也就是SubsystemName了
参考文件EnginePluginsOnlineOnlineSubsystemSourcePublicOnlineSubsystemNames
里面定义了若干平台名称对应(如下图)
发送NMT_Login消息, 带有的字符串, 就都逐个介绍过.
再总的概况一下, 客户端向服务器发送包含玩家昵称, 网络ID, 连接地图参数, 平台名称的内容
给, 这是我的身份证明(玩家昵称与网络ID), 工作单位(平台名称), 来干什么的(连接参数), 放我进去吧!
服务器收到NMT_Login消息, 兜兜转转又走到UWorld::NotifyControlMessage处, 开始处理
老规矩, 先检查
好的, 检查通过, 我看看, 你写的什么.
嗯, 一个登陆请求, 想进去.
先看看信息对不对, 并确认一下.
没问题, 好, 至少我觉得没问题.
我再问问你要去的地方(游戏地图内)允不允许你去.
AGameModeBase, 这个人的信息有问题没? 人你要不要? 有问题就让他滚蛋了.
// 这个时候就是Gameplay里面, 可以处理很多, 比如人满了, 不让进, 参数不对, 不让进等等
没问题!
听见了没, 还不快gu...等den, 没问题...
...
先生, 欢迎光临, 里边儿请!
AGameModeBase, 招待下新人, 他要去哪里, 干什么?
哦, 好的.
来, 看这张地图, 去这个地方(LevelName)做这个事 (GameMode),
第四回合 NMT_Welcome
客户端又收到一条NMT_Welcome信息
还是在老地方UPendingNetGame::NotifyControlMessage处理
我可以进去了?
嗯, 我要去这个地方干在这个事, 和之前想的不太一样啊!
// 客户端连接服务器时, 请求地图和GameMode是无效的, 以服务器为准
赶紧过去报道!
我要过去报道了, 马上就到!
服务器收到NMT_Netspeed消息, 又走到UWorld::NotifyControlMessage处
日常检查一下, 然后设置一下客户端的网络速度
收到, 速度还挺快!
第五回合 NMT_Join
客户端加载完地图, 准备正式加入服务器
我准备好了!
我准备好了!
服务器又收到一条NMT_Join信息, 兜兜转转还是UWorld::NotifyControlMessage在处理
检测, 通过略
好了, 你的身份牌(PlayerController)生成了, 好好干!
经过这么多道手续, 客户端终于通过了身份验证, 和服务器建立起连接.
客户端就进入到AGameModeBase流程, 见其他文章
结语
- 先这样, 之后再修改修改, 这篇文章憋了好久了... 关联的补充细节的都有一两篇了...
- 骗赞了, 骗收藏了