ET中的网路组件在客户端就是NetOuterComponent,当然,这是包装过的。这里面只是指定了所使用的网络类型,demo中默认使用的是TCP类型的网络。这里使用的组件工厂添加组件,所以会默认执行Awake事件。
NetOuterComponent组件的Awake事件是定义在类NetOuterComponentAwakeSystem中的,值得注意的是,这里还有一个NetOuterComponentAwakeSystem1类,但是该类重写的Awake方法有两个参数。Awake方法会执行NetOuterComponent组件的Awake方法,同时赋值两个属性MessagePacker和MessageDispatcher。
NetOuterComponent组件继承自NetworkComponent组件。NetworkComponent组件的Awake方法会根据选择的NetworkProtocol类型不同,创建不同的AServer类型,如图:
到这里呢,网络组件创建完毕。
网络组件的使用包括收发消息,协议的定义等两部分。这一部分是难点,毕竟我没有做过网络开发(手动哭泣)。 这里使用的是斗地主的demo,在玩家登陆的过程中是会有消息的发送和返回的,可以从这里开始看看网络组件的使用。
面板部分有可能是写在hotfix中的。hotfix的启动是利用LoadHotfixAssembly方法加载了hotfix的dll,然后使用GotoHotfix方法进入了hotfix的主入口函数。跟踪代码,可以看到在HotFix.UIComponent中使用Create方法创建了登录UI界面。
这里,我们来详细分析一下这个过程,点击注册按钮后
1、通过NetOuterComponent组件,创建一个Model层的Session对象。
2、再创建一个Hotfix.Session对象,注意,这里是通过ComponentFactory创建的。并且,Hotfix.Session是和Model.Session有关联的。Hotfix.Session会通过Model.Session发送消息。
3、通过Call方法向服务器发送创角的协议请求。
4、检查协议对象返回的错误码,确认是否注册成功。成功后,调用登录方法(登录按钮触发)。
5、断开连接。
下面来详细看看Call方法中做了什么。首先呢,创建Hotfix.Session后,会执行Awake方法,该方法会将Model.Session对象传递进Hotfix.Session中,同时会在其下面挂载一个SessionCallbackComponent组件,设置该组件中的两个Action委托:
在Hotfix.Session.Call方法中,添加元素到requestCallback字典中去,Key值是一个自增变量,Value值是一个Action委托,在检查错误码后,会把委托设置到线程中去。让后将协议的RpcId属性设置为刚刚的自增变量值,这样,就可以通过该值,将协议和委托相对应。调用Hotfix.Session.Send(byte flag, IMessage message)方法,最后返回一个Task对象。
在Hotfix.Session.Send(byte flag, IMessage message)方法中,会获取到Model.OpcodeTypeComponent组件,通过该组件获取到协议对象的opcode值。该值定义在HotfixOpcode文件中,同时为对应的协议类,设置了标签。总结起来就是,协议类持标签,该标签内部有一个opcode属性,在设置标签的时候,会定义该值,该值则定义在HotfixOpcode文件中。这样就可以通过反射,事先收集所有的协议对象,并且和opcode值对应,缓存在Model.OpcodeTypeComponent组件组件中。最后再调用Model.Session.Send(byte flag, ushort opcode, IMessage message)方法,这里除了协议对象参数,还有opcode值,以及一个flag值(具体还未知道用途)。
Model.Session.Send(byte flag, ushort opcode, IMessage message)方法中,会使用流来发送协议对象。这里面会对协议对象进行进一步的包装(设置包头等等)。值得注意的一点是,这里的流对象其实是对应的Model.TChannel对象(因为选择的是TCP连接)中的流对象。
通过调试,可以发现,写入流的过程如下:
1、在流当前位置,空出3个单位的位置后设置指针。
2、设置流的长度为3个单位(可能涉及到扩容)。
3、通过调用ProtobufHelper.ToStream方法,将协议内容写入流。根据调试,可以发现,写入流的方法其实是写在协议类中的。
4、最后再设置下流的指针到写入的数据的末尾。
最后再设置byteses列表(两个byte数组元素),将传递过来的flag参数设置成第一个byte数组的第一个元素。再把opcode值设置到第二个数组中去(通过位运算,将数组前两个元素设置为指定值)。
接下来就是对数据进行包装了,这个byteses列表就是一个处理的中转站。将flag值放在了流的第一个位置,将opcode值放在了流的第二个位置。
接着调用channel对象的Send(MemoryStream stream)方法。该方法中会设置缓存字节数组cache的前几个元素,设置完成后,写入到TChannel.sendBuffer对象头部,相当于再一次进行包头(这里设置的,其实是协议数据的长度)。然后将流中的数据数据写入到TChannel.sendBuffer对象中去(详见这篇文章),最后调用TService.MarkNeedStartSend方法标记该TChannel对象是有数据待发送的。
之后,回到主线程。执行TChannel.OnConnectComplete方法,进入StartRecv方法,确认是否连接成功。发送完成后,在主线程调用TChannel.OnSendComplete方法,确认是否发送完成。再次回到主线程,调用TChannel.OnRecvComplete方法。
在TChannel.OnRecvComplete方法中,会从接受到的包中,解析出数据。解析出流数据后,执行Session.Run方法。在Run方法中,重新解析出flag,opcode和协议对象内容,其步骤和先前包装数据时的步骤相反:
1、将流的指针向后移动3个单位。
2、获取第一个位置的flag值和第二个位置的opcode值。
判断opcode值是否是Hotfix层的消息,是则执行SessionCallbackComponent.MessageCallback方法,该方法在创建Hotfix.Session后就进行了赋值。
进入Hotfix.Session.Run方法后,就需要获取协议内容了,
1、通过OpcodeTypeComponent组件,根据opcode值,创建对应的协议对象。
2、通过ProtobufPacker.DeserializeFrom方法,反序列化出数据。
拿到数据后,根据拿到的flag值,将消息内容对象强转为IResponse对象,根据RpcId值,判断requestCallback中有么有对应的委托方法,并且执行该委托。所以,flag的目的,应该是判断客户端发过去的协议是否是需要返回的类型。而该委托定义在Hotfix.Session.Call方法中,判断了一下错误码,然后返回了一个Task
如果是不需要返回的协议,那么会使用MessageDispatherComponent组件来处理, 关于该组件可以看文章《ET框架学习——OpcodeTypeComponent组件和MessageDispatherComponent组件》。使用Handle方法来分发该消息。 处理逻辑和之前需要返回的消息不同点在于:需要返回的协议的回调方法是通过RpcId来区分的,但是不需要返回的协议的回调方法则是通过opcode值区分的。
到这里,一个网络请求协议的发送和接受就完成了。
后面就是登录环节,发送登录协议,在收到登录返回后,利用登录地址创建gate服务器,然后登录gate服务器,后面玩家与服务器的通讯都是通过该gate服务器转发。