ET服务器框架学习笔记(十二)
文章目录
前言
前篇记录了抽象类NetworkComponent,本篇介绍ET真正加载使用的类:NetInnerComponent与NetOuterComponent。
一、NetInnerComponent与NetOuterComponent
NetInnerComponent称为内网组件,NetOuterComponent称为外网组件。基本上所有服务都需要内网组件,而外网组件不是所有服务都需要,可以看到:Location服务,Map服务是没有外网组件的,说明这两个服务,都是通过内网进行转发出去的。
下面再看看具体的逻辑。
二、两者的区别
1.NetOuterComponent
通过NetOuterComponent几个相关的System代码可以看到,基本上与NetworkComponent使用上没有太多变动,主要是对NetworkComponent的初始化工作,消息解析器用的ProtobufPacker,派发器用的OuterMessageDispatcher。
self.Awake(self.Protocol, address);
self.MessagePacker = new ProtobufPacker();
self.MessageDispatcher = new OuterMessageDispatcher();
主要看看派发器,从上篇文章,可以看到当Session收到数据后,进行一系列处理后,如果不是IResponse协议,就会调用NetworkComponent的MessageDispatcher的Dispatch方法:
if (!(message is IResponse response))
{
this.Network.MessageDispatcher.Dispatch(this, opcode, message);
return;
}
如果是外网组件,则调用的OuterMessageDispatcher的Dispatch。
1.OuterMessageDispatcher的DispatchAsync方法
先看代码:
public async ETVoid DispatchAsync(Session session, ushort opcode, object message)
{
// 根据消息接口判断是不是Actor消息,不同的接口做不同的处理
switch (message)
{
case IActorLocationRequest actorLocationRequest: // gate session收到actor rpc消息,先向actor 发送rpc请求,再将请求结果返回客户端
{
long unitId = session.GetComponent<SessionPlayerComponent>().Player.UnitId;
ActorLocationSender actorLocationSender = await Game.Scene.GetComponent<ActorLocationSenderComponent>().Get(unitId);
int rpcId = actorLocationRequest.RpcId; // 这里要保存客户端的rpcId
long instanceId = session.InstanceId;
IResponse response = await actorLocationSender.Call(actorLocationRequest);
response.RpcId = rpcId;
// session可能已经断开了,所以这里需要判断
if (session.InstanceId == instanceId)
{
session.Reply(response);
}
break;
}
case IActorLocationMessage actorLocationMessage:
{
long unitId = session.GetComponent<SessionPlayerComponent>().Player.UnitId;
ActorLocationSender actorLocationSender =await Game.Scene.GetComponent<ActorLocationSenderComponent>().Get(unitId);
actorLocationSender.Send(actorLocationMessage).Coroutine();
break;
}
case IActorRequest actorRequest: // 分发IActorRequest消息,目前没有用到,需要的自己添加
{
break;
}
case IActorMessage actorMessage: // 分发IActorMessage消息,目前没有用到,需要的自己添加
{
break;
}
default:
{
// 非Actor消息
Game.Scene.GetComponent<MessageDispatcherComponent>().Handle(session, new MessageInfo(opcode, message));
break;
}
}
}
代码有些长,一步步分析:
- 首先这是个异步无返回值的方法,意味着可以异步await调用,但貌似没看到await调用的地方,走的是同步调用。
- 首先传入的参数是:一个session,一个协议号,一个协议数据。
- 根据消息内容做不同处理,首先是IActorLocationRequest类消息。个人理解:这类消息主要是客户端向Map等服务发送消息时,都是走的Gate转发,之前也提到了MAP并没有外网组件,因此与Map等服务进行通讯时,都使用的Gate服务进行转发。这里需要了解ET的ACTOR机制,下篇单独来梳理一下ACTOR机制,如果想提前了解的话,可以参考ET的BOOK,ETBOOK下面两篇文章看看
可以理解为ET可以分为多进程服务,当客户端向Map发消息时,他发送一个Actor消息,这样当Gate接收到这个消息后,通过Actor的方式向Map发消息并等待回复,然后再回复给客户端。具体等下篇来说明
- IActorLocationMessage actorLocationMessage:与上面一样,下篇详细梳理,这里理解为不需要带回复消息的actor类消息,只负责发送就好。
- IActorRequest actorRequest,与IActorMessage actorMessageET并没有使用,也归到Actor消息里面下篇记录
- 非Actor消息,理解为在本服务中,直接处理调用到服务的MessageDispatcherComponent的Handle进行处理。
- Handle方法,根据传入的消息类型协议号,得到所有相关协议的处理类实例,然后调用他们的Handle即完成了一次对协议数据的处理。
这里需要区分的是两种协议类型,一种是带返回的,一种是不带返回的。不带返回的,才能在session中进行派发,走到派发器。如果是返回数据,则肯定之前是有回调的处理,则交给回调处理了。
2.NetInnerComponent
NetInnerComponent比NetOuterComponent组件要多一些处理,即Dictionary<IPEndPoint, Session> adressSessions的处理。内网组件用于处理各个服务之间的通信,他们的session很少发生变化,所以通过adressSessions,可以做一些缓存处理。通过Session Get(IPEndPoint ipEndPoint)
,要么从缓存中拿到对应服务连接的Session,要么通过Create的方式主动连接对方的服务。
public override void Load(NetInnerComponent self)
{
self.MessagePacker = new MongoPacker();
self.MessageDispatcher = new InnerMessageDispatcher();
}
可以看到内网使用的序列化方式与外网不同,使用的MongoPacker进行序列化与反序列化。这样做的好处是方便对数据进行持久化处理,如果是通过proto的话,可能还需要二次处理(个人理解),最典型的就是将一个map服上的unit(玩家游戏对象)迁移到另外一个Map服时,可直接将unit对象本身给序列化传给另外的Map服上,这样就太方便了。
注意点:当使用mongo序列化的时候,可以使用[BsonIgnore]对字段进行忽略。这里有个重要的特性就是如下代码,所有component会忽略InstanceId字段,当使用Bson反序列化时,会调用component的无参构造函数对InstanceId重新赋值。这样当将一个对象迁移到另外一个服务(进程)上时,会重新给InstanceId赋值,这样保证了InstanceId在新的服上是唯一的。然后发送ACTOR消息时就需要通过LOCATION的方式,进行定位。
public abstract class Component : Object, IDisposable
{
[BsonIgnore]
public long InstanceId { get; private set; }
1.OuterMessageDispatcher的Dispatch
public void Dispatch(Session session, ushort opcode, object message)
{
// 收到actor消息,放入actor队列
switch (message)
{
case IActorRequest iActorRequest:
{
HandleIActorRequest(session, iActorRequest).Coroutine();
break;
}
case IActorMessage iactorMessage:
{
HandleIActorMessage(session, iactorMessage).Coroutine();
break;
}
default:
{
Game.Scene.GetComponent<MessageDispatcherComponent>().Handle(session, new MessageInfo(opcode, message));
break;
}
}
}
可以看到与外网组件不同,内网派发组件Dispatch并不是异步支持的。
内网派发组件,确认所有消息都是服务器内部之间进行的通信,所以处理比较简单。
1.IActorRequest类型的消息,调用HandleIActorRequest(Session session, IActorRequest message)进行处理。首先通过CoroutineLockComponent锁定Actor,这个也放到下篇详细分析。
2.IActorMessage类型消息,也与上面相同放到下篇。
3.其他消息同外网派发组件,交给MessageDispatcherComponent直接处理。
总结
本篇简单说了下内网与外网组件,其中核心设计,都是对MessagePacker即协议解析类,以及MessageDispatcher消息派发类进行实例化,不同的是内网组件会缓存各个服务模块IP对应的session。
不同的MessageDispatcher类型,核心思想都是通过消息类型进行派发。外网OuterMessageDispatcher消息类型分为:IActorLocationRequest,IActorLocationMessage以及其他类型,ActorLocation类型基本上都是需要转发处理的类型,而且需要定位系统的帮助。而内网InnerMessageDispatcher类型,都是处理内部服务通信服务协议的,所以分为IActorRequest,IActorMessage,以及其他类型。
其中其他类型都是交给自己这边的MessageDispatcherComponent进行直接处理,IActorLocationRequest,IActorLocationMessage,IActorRequest,IActorMessage放到下篇进行详细讨论。