前言
Unity5.1为开发者发布全新的多玩家在线工具、技术和服务。该技术的内部项目名称为 UNET,全称为 Unity Networking。这个方案已经被官方弃用了。不进行更新了
但现在项目有用到UNET(现在换成了Mirror网络框架,UNET的替代品)
还是要学习一下网络框架才能更好学习项目内容。我们直接学习Mirror!
1. Mirror介绍
Mirror是一个为Unity 2019/2020 LTS及以上版本设计的高层网络框架,兼容不同底层传输接口.
Mirror的设计就是为了简单易用,快速上手。封装网络底层,一线开发人员可以不需要了解底层的网络传输结构。
允许同时作为客户端或服务器端,所以不需要搭建专用的服务器。
2. 网络通信原理
1. CMD和RPC是干嘛用的?
Mirror的特性,使得客户端服务端逻辑都在一块,那么我们需要一些标签来区别客户端服务端代码。
[Server]/[Client]:用来区别服务端/客户端专用代码
[Command]:用来标记客户端对服务端的远程调用。
底层原理是通过事件调用Cmd 方法,把数据传到 Server 端
[ClientRpc]/[TargetRpc]:远程过程调用(Remote Procedure Calls) ,简称RPC,
RPC用来调用远程计算机上某个函数的方法,这个分为2种:
用来标记服务器端调用所有客户端[TargetRpc]
或者某个特定客户端(ClientRpc)的函数。
底层也是事件机制。
[SyncVar]:用来标记从服务端同步到客户端的变量。
这种特性的数据的修改只能从服务端修改,客户端是没有权限修改的,只能读取。要通过有[ServerCallBack],[Server]的方法修改。
其实就是不断分发玩家的状态数据,这个过程消耗大量的带宽,所以我们要优化带宽数量。
3. Examples/Chat
打包出来,用一个端当服务器,其他端连接 localhost 当客户端,就能达到以下效果
核心就是点击按钮会调用CmdSend,然后就大家都看见了别人发的消息内容?
why?
接下来学习一下Command和ClientRpc 参考:通讯部分:Remote Actions
[Command]
public void CmdSend(string message)
{
if (message.Trim() != "")
RpcReceive(message.Trim());
} [ClientRpc]
public void RpcReceive(string message)
{
OnMessage?.Invoke(this, message);
}
4. Command 实现原理源码学习
命令是从客户端发送到服务器上的一个函数。要把一个函数变成一个命令,只要加上[Command]修饰符,也可以加上Cmd前缀来区分。函数变成命令后,就可以在服务器上运行
并且命令不能被static修饰
那么这么神奇的Command是什么实现的呢?
[AttributeUsage(AttributeTargets.Method)]
public class CommandAttribute : Attribute
{
public int channel = Channels.Reliable;
public bool requiresAuthority = true;
}
Command 属性修饰方法,他生效分成2个部分,①编译后处理;②服务器消息监听
①编译后处理:
找一下 CommandAttribute 的引用,就会发现如下堆栈。
在代码编译结束后,Mirror 会紧锣密鼓的对各个自定义属性进行进行处理,堆栈如下:
CompilationFinishedHook.OnCompilationFinished
Weaver.WeaveAssembly
Weaver.Weave
Weaver.WeaveModule
Weaver.WeaveNetworkBehavior
NetworkBehaviourProcessor.Process
NetworkBehaviourProcessor.ProcessMethods
NetworkBehaviourProcessor.ProcessCommand\ProcessTargetRpc\ProcessClientRpc
生成 CmdThrust 方法,添加到 commandInvocationFuncs 委托列表里面
NetworkBehaviourProcessor.GenerateConstants
NetworkBehaviourProcessor.GenerateRegisterCommandDelegate/GenerateRegisterRemoteDelegate/GenerateSyncObjectInitializer
生成IL代码版本的 委托注册
ProcessCommand 会校验方法和参数有效性(方法不支持泛型、不支持返回值,不支持IEnumerator、Command 方法不能重复)
编译后,会生成 IL 代码,生成一个 CmdThrust 方法,最终调用 NetworkBehaviour 的SendCommandInternal 方法
②服务器消息监听
服务器在启用的时候,会注册监听 OnCommandMessage,然后由 identity 去处理远程调用
identity.HandleRemoteCall
RemoteCallHelper.InvokeHandlerDelegate
③客户端调用
客户端调用带有 Command 标签的方法,会调用到 SendCommandInternal 方法,向服务端发送消息。
服务端通过IL层注册的委托,直接找到对应的方法,进行调用。
这样就实现了远程过程调用。
TargetRpc 和 ClientRpc 原理应该也是一样的,只是调用方向反过来了而已,就不赘述了
5. Examples/Basic 简单食用
打包出来,用一个端当服务器,其他端连接 localhost 当客户端,就能达到以下效果
我对这个案例比较好奇的点就是,这些数据是怎么同步的?SyncVar是怎么实现的。
6. SyncVar 实现源码学习
SyncVar 用来标记从服务端同步到客户端的变量。就是个 ClientRPC 操作。
SyncVar 在函数中是这样定义的:
[AttributeUsage(AttributeTargets.Field)]
public class SyncVarAttribute : PropertyAttribute
{
public string hook;
}
SyncVar实现数据同步分成2部分:①编译后处理 SyncObject初始化;②Update驱动同步
①编译后处理 SyncObject初始化
// 生成 SyncVar 在 IL 的 Get\Set 方法
CompilationFinishedHook.OnCompilationFinished
Weaver.WeaveAssembly
Weaver.Weave
Weaver.WeaveModule
Weaver.WeaveNetworkBehavior
NetworkBehaviourProcessor.Process
NetworkBehaviourProcessor.ProcessSyncVars
SyncVarProcessor.ProcessSyncVar:生成IL的Get\Set方法
注释说,会在构造Weaver的时候调用 InitSyncObject ,收集所有同步对象,然后在OnSerialize/OnDeserialize 的时候进行同步更新
SerializeSyncVars,初始化所有 SyncVar,然后逐帧更新 dirty 的值
②NetworkServer Update 驱动同步
NetworkServer.NetworkLateUpdate
NetworkServer.Broadcast
NetworkServer.BroadcastToConnection
NetworkServer.GetEntitySerializationForConnection
NetworkIdentity.GetSerializationAtTick
NetworkIdentity.OnSerializeAllSafely
NetworkIdentity.OnSerializeSafely
NetworkBehaviour.OnSerialize
NetworkServer 的 NetworkLateUpdate 里有个 Broadcast 广播方法
在广播里,我们会不断去向已经准备好的端,广播序列化好的数据。
7. 服务器监听部分 源码学习。以 KcpTransport 为例子
①初始化绑定
NetworkManager.SetupServer
NetworkServer.Listen
Transport.activeTransport.ServerStart
KcpServer.Start:新建 UDP socket,绑定IP(NetworkManager.networkAddress)和端口(activeTransport.port)
以 KcpTransport 为例子。这个 Transport 是可以传入的,这边用的 KcpTransport。见下图
②Update Tick收到的数据
NetworkLoop.RuntimeInitializeOnLoad
NetworkLoop.NetworkEarlyUpdate
NetworkServer.NetworkEarlyUpdate
以 KcpTransport 为例子 KcpTransport .ServerEarlyUpdate
KcpTransport.TickIncoming
KcpServer.ReceiveFrom
KcpServer.OnData.Invoke(connectionId, message);
NetworkServer.OnTransportData
NetworkServer.UnpackAndInvoke
handler.Invoke(connection, reader, channelId); 根据消息类型,找到对应的委托,进行调用
③业务部分
以一个 ReadyMessage 为例子:
服务端注册 ReadyMessage 委托
RegisterHandler<ReadyMessage>(OnClientReadyMessage);
客户端发送 ReadyMessage
connection.Send(new ReadyMessage());
这样服务端收到消息,就能找到对应委托进行调用
总结
总结一下,就是编译后处理,生成IL代码去调用 NetworkBehaviour 的 SendCommandInternal
服务器在启用的时候,注册消息监听
就这样悄摸摸地藏好了代码,让我们去摸不着头脑,找不着调用
在我看来[Command]就是语法糖,封装了重复,机械的消息注册,消息接收等逻辑到IL层。
参考
Unity3D-network网络相关(一)_alayeshi的专栏-CSDN博客
Unity3D-network网络相关(二)_alayeshi的专栏-CSDN博客
GitHub - vis2k/Mirror: #1 Open Source Unity Networking Library
推荐本站淘宝优惠价购买喜欢的宝贝:
本文链接:https://hqyman.cn/post/9795.html 非本站原创文章欢迎转载,原创文章需保留本站地址!
休息一下~~