前言
- UE4版本4.25
孤傲雕:UE4 网络相关之网络驱动器(UNetDriver)的顺藤摸瓜
接前文, 在扒了一大堆代码之后, 对网络连接最开始的地方, 有了一定的了解.
- 服务器什么时候创建UNetDriver开始监听端口, 支持客户端连接.
- 客户端什么时候创建UPendingNetGame开始连接服务器.
下一步, 自然是进一步深扒, 客户端是如何和服务器建立连接, 直到两边可以发送RPC事件和进行属性同步为止.
客户端开始向服务器进行请求, 尝试创建连接, 主要处理在UPendingNetGame类中
![38f4610352098b5c566ec651b1d9a2cf.png](https://i-blog.csdnimg.cn/blog_migrate/7088e6f1c7bb396c2b791a2f508d5ac5.png)
- 创建UPendingNetGame, 通过NewObject创建对应对象, 构造函数无处理, 略
![c5bdd8e89acd379b4a50fc9accf5fbf5.png](https://i-blog.csdnimg.cn/blog_migrate/20b687edad906c6d6929011f323a0826.png)
2. 调用UPendingNetGame::Initialize初始化函数, 传入FURL信息
这个信息包含了服务器地址、端口、地图信息、连接参数等内容.
![2aeb43dcb96b61e8a863a1801bdd7791.png](https://i-blog.csdnimg.cn/blog_migrate/30cbcb2dde73962c8548ec546a305c3f.png)
3. 调用UPendingNetGame::InitNetDriver, 开始初始化网络驱动
![ddcdbd06787c2bdfaacd29c7ed0eb265.png](https://i-blog.csdnimg.cn/blog_migrate/a7bf5f1f5e3e0afcd17e9474b49547c2.png)
![d6f3002ca172c3f4f3d972362a99c2dd.png](https://i-blog.csdnimg.cn/blog_migrate/db7b6ea98b4b4da65c794260d7bda550.png)
先看上图这个全局变量GDisallowNetworkTravel, 如果为true, 则禁止任何网络连接切换, 在处理切换关卡时拒绝这一请求.
此处客户端与服务器的连接处理与该值对应, 如果不处理, 直接连接错误, 记录日志
![0965f21d05437a66c5dfecafc7856617.png](https://i-blog.csdnimg.cn/blog_migrate/e7940fcf1e686407e74f278c8d404606.png)
然后, 扫一眼, 明显分为创建UNetDriver网络驱动和初始化两部分
![b2fbf735db43933eba8a78f8c9a6a473.png](https://i-blog.csdnimg.cn/blog_migrate/14f176c16e0aea03c174a882fe29915d.jpeg)
创建初始化UNetDriver相关, 此处不做扩展, 见其他文章
![14f180dc30b9209d70eb3ee3fb153830.png](https://i-blog.csdnimg.cn/blog_migrate/439e8e9bb1db985c417e807a0233f421.jpeg)
如图广播FNetDelegates::OnPendingNetGameConnectionCreated
![af179d61c5a34b603f7ce8c05586e9e6.png](https://i-blog.csdnimg.cn/blog_migrate/768d5439150a1de1608ee2dfccae0dc8.png)
![9ed325e3146cc1b29e9b6fad32ecfe07.png](https://i-blog.csdnimg.cn/blog_migrate/a0eafe67146445d8f02f7beaba60f35b.png)
如果NetDriver中的ServerConnection的PacketHander存在, 会先调用PacketHandler的BeginHandshaking建立连接, 完成后调用SendInitialJoin.
不存在, 则直接创建.
在做了这么多准备工作(见其他文章)后.
这个时候, 和服务器一个沟通的渠道就建立完成了, 接下来就开始和服务器友好的"沟通"一下.
第一回合 NMT_Hello
![eb361bfdd0facbb0d9e7e1861705536a.png](https://i-blog.csdnimg.cn/blog_migrate/9519d2091ca3f76ebf0023c6fd854dac.jpeg)
客户端先向服务器友好礼貌打一声招呼!
Hello!
![47f3fd68c077769e309974da8716c5d1.png](https://i-blog.csdnimg.cn/blog_migrate/2504aab0994418dfe967ca105a33e4a0.png)
服务器收到这一NMT_Hello消息
从UWorld::Tick函数的多播转交到UNetDriver
从UNetDriver再转交给与客户端对应的UNetConnection, 并进一步处理
最后, 又反手丢回给UWorld::NotifyControlMessage
![fce30531575762de0eebca5b37e4736f.png](https://i-blog.csdnimg.cn/blog_migrate/4b0d1914192de161ff3e92932b20691c.png)
ServerConnection, 和服务器的连接, 存在自然就是客户端.
嗯, 那if里面是客户端逻辑, else里面就是服务器逻辑.
![fbfc3fa1719cd4bdc7b321a6d78e2392.png](https://i-blog.csdnimg.cn/blog_migrate/f3bc32e883ddc33a8caae2d92443b5eb.jpeg)
废话不多说, 否管哪来的, 先检查!
![1e9b3fb5f847e5117a705c834a3ced53.png](https://i-blog.csdnimg.cn/blog_migrate/a67dc3adebe458188b238a3ac87c73c8.png)
不合格? 简单, GoodBye! 走你!
![1ef058ce6d910ef62613b97caef9163f.png](https://i-blog.csdnimg.cn/blog_migrate/4b2f51d6649a562146a85d170b33cc07.jpeg)
嗯, 检测合格了,.?
好孩子, 来, 再让我看看, 有没有先和我问个好, 打个招呼.
好, 看起来像个样子.
![150e938e315ab59bc6082c10c28b7009.png](https://i-blog.csdnimg.cn/blog_migrate/cfcea850bdb80056cc246054754657dd.jpeg)
孤傲雕:UE4 网络相关之网络版本获取与比对
但我们这可不是什么随随便便的地方, 打招呼的礼仪对不对? 是现在最流行的, 最高贵最优雅的那一种吗?
不是???
???
...
滚回去学, 学完了再说...
![bf7518381d5b53af2595dbe1993773db.png](https://i-blog.csdnimg.cn/blog_migrate/baa4ef8a803103ec7b3e1daf09cee37f.png)
然后客户端就收到了服务器的NMT_Upgrade消息
从UEngine::TickWorldTravel转发到UPendingNetGame::Tick再
从UEngine::TickWorldTravel函数的转交到UPendingNetGame::Tick
从UPendingNetGame再转交给对应的UNetConnection, 并进一步处理
最后, 又反手丢回给UPendingNetGame::NotifyControlMessage
![44b177e9a202f40230b98d787ff91120.png](https://i-blog.csdnimg.cn/blog_migrate/dd4d2e0897d22f25bd23a50bc7cbcf82.jpeg)
然后, 客户端就多播UEngine::BroadcastNetworkFailure, 类型为ENetworkFailure::OutdatedClient, 并记录对应错误日志
然后苦兮兮的更新版本去了.
第二回合 NMT_Hello
客户端好好的学了学最新的礼仪(更新到最新的客户端版本), 再一次向服务器友好礼貌打一声招呼!
Hello!
![b8f4e39443772dc64f326604c955a15a.png](https://i-blog.csdnimg.cn/blog_migrate/ac78e34c4668024bdef9e86b2cee1154.png)
服务器再次看到了客户端.
嗯, 这次礼仪对了, 口令!
没有口令? ...
嗯, 对就是没有口令!
// 如果有口令, 会对口令进行验证, 并考虑是否传输进行加密, 见其他文章
![616b7fe76c4821a5b4de50d7bd46399d.png](https://i-blog.csdnimg.cn/blog_migrate/7d89c8f3aa7b033a0a0b1e9d1d5dadd6.jpeg)
![5ded6d46fae142469240dc236b6cbf60.png](https://i-blog.csdnimg.cn/blog_migrate/737534e422938725f8b59953392a4d84.png)
通过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处理
![e6c4e4a9a8efe05575f24988c95999d5.png](https://i-blog.csdnimg.cn/blog_migrate/ff29536fe87e4ee41d63f9847dfb227f.jpeg)
![ae4233876e0a32a3002040d75e7bdd5c.png](https://i-blog.csdnimg.cn/blog_migrate/edc23997312472d4b13d17f4a0ce6e75.png)
然后PartialURL数据, Host清空, Port取默认值
嗯, 与服务器连接已经建立了, 这两个值没用处了
![35b50261e9c7deaba987b030b41aa823.png](https://i-blog.csdnimg.cn/blog_migrate/bdae98aedc74c7195298b44ede0f68d5.jpeg)
然后如图
![d6faca2fd8f167e79e7f408de4a41938.png](https://i-blog.csdnimg.cn/blog_migrate/be893e45ed5dfa7f46593ad809ff8399.png)
当ULocalPlayer::GetNickname有值时候, 添加一个Name参数
![e9bf354c2e02a2ec45abac43ee07e6d7.png](https://i-blog.csdnimg.cn/blog_migrate/945805a1fd14e0e7f38900fc5d6e9d7b.png)
当ULocalPlayer::GetGameLoginOptions有值时, 添加对应参数
然后设置UConnection的PlayerID, 通过ULocalPlayer::GetPreferredUniqueNetId获得
例如 : L"DESKTOP-10Q6V1I-FCDB05414DB72C66F61B6E80E315FEA9"
![f6659f0c025c81bece9f67f36a730d8b.png](https://i-blog.csdnimg.cn/blog_migrate/df951d64958e4b77723549ef8657a510.png)
然后获得OnlinePlatformName, 平台名称
![4bba48d3ba309b3f70f2d63af7e9f5f6.png](https://i-blog.csdnimg.cn/blog_migrate/34fca5134005a4e74bdc3893cc38f772.png)
![273d49617e3009a454acfc6c6219b8dc.png](https://i-blog.csdnimg.cn/blog_migrate/0f04641be43e82066fff00f5bd90c3fe.png)
通过UGameInstance::GetOnlinePlatformName, 然后找UOnlineEngineInterface::GetDefaultOnlineSubsystemName获得
UOnlineEngineInterface是个接口类, 自然不同平台有对应的子类, 有不同的实现
![21707c67ee3f6755591356a08250d654.png](https://i-blog.csdnimg.cn/blog_migrate/ce487ad7442a0b6c4723277dbd888e4b.png)
![98e294784dc430d4ef84c8623607f531.png](https://i-blog.csdnimg.cn/blog_migrate/c3f5e253201bc5ecc572dddf17618fbc.png)
其中, UOnlineEngineInterfaceImpl是一个常用实现, 从IOnlineSubsystem中获得对应的平台名称
![a858b4bd9685edfec33f787c1abc747a.png](https://i-blog.csdnimg.cn/blog_migrate/627e3a527f37a8cbb340771763848c86.png)
![738bfb2252c8cad12a70e8adfa071c60.png](https://i-blog.csdnimg.cn/blog_migrate/5ffbc560eb27d11ad4f0b4a2109cab85.png)
同理又得, FOnlineSubsystemImpl又是IOnlineSubsystem的一个常用实现, SubsystemName该属性就是平台名称
![c25729b3ae0741b5e47b5334c1f37803.png](https://i-blog.csdnimg.cn/blog_migrate/e84c59c7d099ac1c46882eafe45ab237.png)
![889e283602b8fcdfac7d8d0e03c7d3c5.png](https://i-blog.csdnimg.cn/blog_migrate/7828be53388e410d83139523f662a75b.png)
以FOnlineSubsystemSteam为例, 它将STEAM_SUBSYSTEM作为SubsystemName传入, 最后平台名称, 也就是SubsystemName了
参考文件EnginePluginsOnlineOnlineSubsystemSourcePublicOnlineSubsystemNames
里面定义了若干平台名称对应(如下图)
![83b5e74a774bdf647909504efc45f00d.png](https://i-blog.csdnimg.cn/blog_migrate/363d51d3d5493e346fa83fbf51acade2.jpeg)
![e6a9737471eb1c31eb969785bf8ef216.png](https://i-blog.csdnimg.cn/blog_migrate/61ed31d80664fe6fdcab61c1d7ddee62.png)
发送NMT_Login消息, 带有的字符串, 就都逐个介绍过.
再总的概况一下, 客户端向服务器发送包含玩家昵称, 网络ID, 连接地图参数, 平台名称的内容
给, 这是我的身份证明(玩家昵称与网络ID), 工作单位(平台名称), 来干什么的(连接参数), 放我进去吧!
服务器收到NMT_Login消息, 兜兜转转又走到UWorld::NotifyControlMessage处, 开始处理
![6ceb7171f2d8733a8d89b00023cead2a.png](https://i-blog.csdnimg.cn/blog_migrate/a4da5153e50b29a12c65788a570cac10.png)
![985fa245f27c86ecee7b2a28ee76ce2b.png](https://i-blog.csdnimg.cn/blog_migrate/9a6b5c18b097e8922258290540de6432.jpeg)
老规矩, 先检查
![2f608d9f775dae1668176d00ba1ed3e6.png](https://i-blog.csdnimg.cn/blog_migrate/abc244115cb38a7a0071961c42df5dee.jpeg)
好的, 检查通过, 我看看, 你写的什么.
嗯, 一个登陆请求, 想进去.
![56f36ca1a603c2e4678eff30aeebd3dd.png](https://i-blog.csdnimg.cn/blog_migrate/fcd0a5fb4d8aeeffcac570b2707ab66a.png)
![cc7606ed76c1bb56c00b8c1caa4a1900.png](https://i-blog.csdnimg.cn/blog_migrate/4812d7ef62c15aa508897862457bc1f0.jpeg)
![ef5ecae0862fff40f81b6160639f61e5.png](https://i-blog.csdnimg.cn/blog_migrate/900588f1e54f80796086c6316405e5fc.png)
先看看信息对不对, 并确认一下.
![8bc7c409e2d7c39b22afed4095585836.png](https://i-blog.csdnimg.cn/blog_migrate/dba58351b954f5ef5a7dfa40f7054954.png)
![23c1cb09972358fcba8309b82aacdf17.png](https://i-blog.csdnimg.cn/blog_migrate/de6397ff266bfcab5ab42834f7e622d8.jpeg)
没问题, 好, 至少我觉得没问题.
我再问问你要去的地方(游戏地图内)允不允许你去.
AGameModeBase, 这个人的信息有问题没? 人你要不要? 有问题就让他滚蛋了.
// 这个时候就是Gameplay里面, 可以处理很多, 比如人满了, 不让进, 参数不对, 不让进等等
![f3650caf59d37b35c4def347190aec1e.png](https://i-blog.csdnimg.cn/blog_migrate/53334c39d48b8a00036085d5ff713793.jpeg)
没问题!
听见了没, 还不快gu...等den, 没问题...
...
先生, 欢迎光临, 里边儿请!
![e88c8b29aaf7033b7cc879cf21004d92.png](https://i-blog.csdnimg.cn/blog_migrate/07f91c48dfacaebfcb1b287a606869d9.jpeg)
![75180d8dfab145fd492334c8455621af.png](https://i-blog.csdnimg.cn/blog_migrate/b7e32a02f44d77b5d9aa1438dc277584.png)
![1590da6a4e41b53e28c0cdb59174918a.png](https://i-blog.csdnimg.cn/blog_migrate/e783e2c970f35d3bdd668026eb824506.png)
AGameModeBase, 招待下新人, 他要去哪里, 干什么?
![82069cdee758d811a10a37d092302616.png](https://i-blog.csdnimg.cn/blog_migrate/c7fdb6c1be088fb621bd7f09c48d928f.png)
哦, 好的.
来, 看这张地图, 去这个地方(LevelName)做这个事 (GameMode),
![56cbecd54ee687ffcf54045fb0b7f310.png](https://i-blog.csdnimg.cn/blog_migrate/932cc2f262f0d3a1161bc6df6670bba2.png)
第四回合 NMT_Welcome
客户端又收到一条NMT_Welcome信息
还是在老地方UPendingNetGame::NotifyControlMessage处理
![00ee06d5042ddace0c733e2babdd91e0.png](https://i-blog.csdnimg.cn/blog_migrate/dccc65c716fa525befad2e1d95350918.png)
我可以进去了?
![ac15cb4e82a8addf5fa07f2dfc0469be.png](https://i-blog.csdnimg.cn/blog_migrate/6ab3e09c8cdcf12717741816bde5c18e.png)
嗯, 我要去这个地方干在这个事, 和之前想的不太一样啊!
// 客户端连接服务器时, 请求地图和GameMode是无效的, 以服务器为准
![33bd3330e542d19541310ddbc85c45c3.png](https://i-blog.csdnimg.cn/blog_migrate/f9dd6edf60b467de103b2cba30016bf6.png)
![07e7dace3277d061f7c297ca3fa6b8cd.png](https://i-blog.csdnimg.cn/blog_migrate/2a71f2b4daff38f12d8939ac9874603c.jpeg)
赶紧过去报道!
![a34f792ee9383ed4de68e3a2cb1f581f.png](https://i-blog.csdnimg.cn/blog_migrate/b93193f3306b4ee1fd4f3c1d3a171e2f.png)
我要过去报道了, 马上就到!
服务器收到NMT_Netspeed消息, 又走到UWorld::NotifyControlMessage处
![d02001456df0344d4c41b241b57f9ccc.png](https://i-blog.csdnimg.cn/blog_migrate/6c1bc8f3fbc6a60fbe2fd6f2a8936a00.png)
![3d96bc1300add2ec8ce7602948455041.png](https://i-blog.csdnimg.cn/blog_migrate/e56ebf4ca43876b0f4892ce67420ce7d.jpeg)
日常检查一下, 然后设置一下客户端的网络速度
![eb360cbb523d29d122c23e86f2558046.png](https://i-blog.csdnimg.cn/blog_migrate/2b7135085c1b9e2d5256d6c6baab23f8.png)
![a7031aece202a92e5a10d05ff5e5b70a.png](https://i-blog.csdnimg.cn/blog_migrate/c39984648b186f7f78abdb7debfd83f2.png)
收到, 速度还挺快!
第五回合 NMT_Join
![03390f3f5f1acb022425b879c73e0294.png](https://i-blog.csdnimg.cn/blog_migrate/72de97e8908ff8227e37184979f379b7.jpeg)
客户端加载完地图, 准备正式加入服务器
![f8987509a73f758ced9bf6eeb34fe9ab.png](https://i-blog.csdnimg.cn/blog_migrate/e3dda07a90857bcb351bec51c1f35f76.png)
我准备好了!
我准备好了!
服务器又收到一条NMT_Join信息, 兜兜转转还是UWorld::NotifyControlMessage在处理
检测, 通过略
![ce6a85d359f75dfca6de6edd3b9fee09.png](https://i-blog.csdnimg.cn/blog_migrate/c63d59d544bc09dedc3d12796e46b6f7.png)
![3f135cc0622738085cf2300b76629b64.png](https://i-blog.csdnimg.cn/blog_migrate/f40cbd6257d2043f7494e80b2e58484f.jpeg)
![06a30677e5b1fa610d7c91d8803bb752.png](https://i-blog.csdnimg.cn/blog_migrate/cc081b46aea7a4d281b4635fabb31420.jpeg)
好了, 你的身份牌(PlayerController)生成了, 好好干!
经过这么多道手续, 客户端终于通过了身份验证, 和服务器建立起连接.
客户端就进入到AGameModeBase流程, 见其他文章
结语
- 先这样, 之后再修改修改, 这篇文章憋了好久了... 关联的补充细节的都有一两篇了...
- 骗赞了, 骗收藏了