在ET的客户端中,使用网络要用到以下几个组件(不介绍基础设施)。
组件名称 | 组件作用 |
---|---|
OpcodeTypeComponent | 消息类型加载组件。加载标记了【MessageAttribute】注解的类,作为消息执行的类型,并存储在这个组件的opcodeTypes字段下,提供后续方法使用。 |
MessageDispatcherComponent | 消息分发组件,加载消息处理器。加载所有标记了【MessageHandlerAttribute】属性的类,并提供Handle方法,将传入的MessageInfo发送给指定的消息处理器 |
NetworkComponent | NetworkComponent是其他上层网络访问组件的基类,这个类型在服务端和客端项目公用。用于 指定该协议、指定构包解析方式、指定消息转发器,开始socket监听、创建session。内部有一个AService字段。抽象了udp和tcp协议的连接工厂,用于创建channel使用tcp做服端的时候可以创建多个Session,每个Session都是一个连接的高层抽象。这个类型在服务端和客户端项目公用。其提供的MessageDispatch和MessagePacker仅在客户端项目使用。一个是客户端消息转发器,一个是客户端消息打包器。 |
NetOuterComponent | 继承自NetworkComponent,用于对外网的连接。可以在Awake中指定协议,指定包解析器。客户端使用的session都是通过这个组件创建的。需要切换网络协议或者数据序列化方式就在这个组件的Awake方法中指定对应的参数就可以了。 |
SessionComponent | SessionComponent 组件用于存储Seesion,目前客户端只会存在一个在用的session。登录后获得的GateSession会存储在这个组件中。会在之后的对服务器端的调用中使用这个session。 |
ClientFrameComponent | 用于处理帧同步。 |
很重要的Session对象的解释
每个Session都是一个连接的高层抽象。
向session另一端做rpc调用可以调用它的Call方法,发送消息可以调用Send,
二者的区别在于是否需要服务器响应一个反馈消息。
定义消息或者rpc请求的时候使用Message标签设置消息类型与opcode的对应关系。
session是由NetworkComponent创建的,参数是 NetworkComponent的本类实例 + 用于收发字节码的AChannel实例。这个AChannel则是由NetworkComponent中的AService工厂创建的。
流程:
1创建session后(调用构造函数),session开始StartRecv()方法,这个方法循环通过异步方法调用channel的Recv()方法,接收链接另一端发来的数据。
2接收到消息后调用Run方法,run方法检查接收到的数据包是否有压缩,并指向相应的操作。
3处理完压缩解压操作后交给RunDecompressedBytes,该方法比较厉害,调用绑定在Scene实体上的NetWorkConponent上的注册的解包器(默认是Bson)解包。
4解包操作结束后就将其交给绑定在Scene实体上的NetWorkConponent上的messageDispatcher做转发。转发给相应的handler处理。
在大致了解了各组件的作用后,需要有一个大致的流程,把几个组件串联起来,实现客服端的网络功能:
网络流程如下:
1. OpcodeTypeComponent加载封装好的网络消息。
2. MessageDispatcherComponent加载所有的Handler
3. 开启网络监听。NetOuterComponent加入到Scene,我们使用udp,做如下操作:、
- a) 通过工厂从对象池中取出实例,调用父类Disposer的构造方法,加入到对象事件中心。
- b) 调用父类NetworkComponent的构造方法,创建对应协议的连接工厂(AService)。
- c) 调用NetOuterComponent的Awake()方法,指定网络协议、消息解包器和消息转发器。
- d) NetworkComponent因为注册了Update,所以会在每一帧执行Update,Update中会执行service的update。service的update中会执行poller的Update,这个Update实际上是 每一帧都要轮询出所有的主机上的队列事件,并且调用其委托。这些事件包括:开始连接、接收数据、断开连接。这些事件的触发,会调用poller实体上的socket的对应的响应方法。 socket事件的委托是在创建UChannel时注册的。
- e) UChannel的OnRecv()将接受到的消息放入到队列中,等待被Session轮询调用UChannel. Recv()方法取出。
- f) 取出消息后就会调用给NetOuterComponent指定的消息转发器。
由于是客户端,所以使用ClientDispatcher作为消息转发器。这个消息转发器在接收到帧循环消息后会进行客户端时间补偿处理。否则就直接将普通的rpc等消息交给MessageDispatcherComponent去做转发了。当然,帧循环消息最终也会交给MessageDispatcherComponent做转发。 - g) Handle接收到消息后作出最终的处理。
- h) D-g步骤是循环执行滴。
- i) 底层enet等不深入讨论,对我等透明。
- j) 到此,网络设施已经创建完毕,网络监听已经开启,并等待使用。
4. 创建对外连接。创建对外连接指的是连接到服务器。一般情况下从登陆开始创建对外连接。并且存储会话Gatesession。在整个客户端游戏生命周期中,Gatesession是一直存在的而且只有一份。
- a) 使用NetOuterComponent创建session。需指定要连接的地址和端口。这时候创建的session一般会是登录服务器,因为登录服务器在登录成功后会返回一个网关地址。
- b) 然后再根据这个网关地址创建游戏使用的session并且存入到SessionComponent中。
- c) SessionComponent要加入到Scene实体保存。
5. 使用session通讯。
- a) await SessionComponent.Instance.Session.Call<G2C_LoginGate>(new C2G_LoginGate() {Key = r2CLogin.Key})
- b) 详细参考doc中的《网络层设计》使用说明书。
6. 拓展网络功能以及建议。
- a) 这个嘛,仁者见仁啦。
- b) 注意。使用c#的异步语法实现异步网络调用,是一件很舒服的事情,请详细研究异步语法。并使用之。
- 登录流程示例讲解:
//TODO
在掌握了ET客户端网络功能之后基本就掌握了ET的客户端框架。其他的一些框架特性是为了增加功能方便开发,当然如果实在掌握不了或者不喜欢用某个组件,可以用其他的方式实现,比如那个行为树。当然,一定要举一反三,通过学习研究网络组件,对整个et框架有个更深的认识,能够理解组件设计模式的精髓。组件包含数据和功能、实体包含组件,逻辑就是整个系统调用组件的过程。这也是最近闹得沸沸扬扬的ECS。另外,此处只是客户端的逻辑,服务端的逻辑TODO。