ET6.0服务器框架学习笔记(三、一条内网普通actor消息)
上篇文章对协议的写法,最简单的一条登录协议使用作为分析对象,本篇针对内网服务器之间的一条actor消息进行使用解析:R2G_GetLoginKey协议
文章目录
一、内网普通Actor协议流程
1、R2G_GetLoginKey协议的作用
R2G_GetLoginKey协议在ET6.0中已经写好,其主要目的就是从Realm服务发送给一条actor消息给Gate服,向它注册并申请一条登陆许可,即常说的token,或者叫key也行。
玩家登陆的主要流程:
- 连接Realm服务,验证登陆用户与密码
- 如果验证通过玩家需要与其他服务模块进行交互,不会要求玩家再进行一次密码认证。使用一种认证机制,即通过Realm服向Gate服请求一个认证key,交给玩家。
- 当玩家连接Gate服时,拿着申请好的key,直接登陆Gate服务即可通过Gate服的允许。
其中Realm服向Gate服请求一个认证key,需要用到R2G_GetLoginKey协议。
协议分析:由于ET6.0是分布式架构,所以可能会有多个GATE存在的情况,且收到这条协议的对象应该是GATE服代表的scene实体,且是需要返回消息的。
结论:R2G_GetLoginKey消息,应该是一个带返回的协议,因此具有返回协议:G2R_GetLoginKey。而且是发送给scene实体的,所以如下定义消息结构:
重点内容:
- R2G_GetLoginKey协议类型:IActorRequest,G2R_GetLoginKey协议类型:IActorResponse。因为代表Gate服务的scene实体不会发生改变到另外一个进程中的情况,所以消息类型就是普通的Actor消息。
- 消息内容就是发送一个rpcid,actorid,与对应的玩家account,返回一个对应rpcid与一个gateId。
2、R2G_GetLoginKeyHandler的发送与处理
6.0中服务器内部发送普通actor的协议,采用单独的发送方式,即通过ActorMessageSenderComponent
单例类进行发送。
// 向gate请求一个key,客户端可以拿着这个key连接gate
G2R_GetLoginKey g2RGetLoginKey = (G2R_GetLoginKey) await ActorMessageSenderComponent.Instance.Call(
config.InstanceId, new R2G_GetLoginKey() {Account = request.Account});
只需要new一个R2G_GetLoginKey类,然后填上自己要发送的内容即可。需要关注的就是目标对象,即Call的第一个参数,普通actor消息通过ActorMessageSenderComponent发送需要知道对方的actorId,这里使用的是配置生成的对应的InstanceId。
R2G_GetLoginKeyHandler归于于服务器Gate服务模块的处理。
[ActorMessageHandler]
public class R2G_GetLoginKeyHandler : AMActorRpcHandler<Scene, R2G_GetLoginKey, G2R_GetLoginKey>
{
protected override async ETTask Run(Scene scene, R2G_GetLoginKey request, G2R_GetLoginKey response, Action reply)
{
long key = RandomHelper.RandInt64();
scene.GetComponent<GateSessionKeyComponent>().Add(key, request.Account);
response.Key = key;
response.GateId = scene.Id;
reply();
await ETTask.CompletedTask;
}
}
上一篇有分析过协议处理类的运作方式。
所以:
- 因为是普通Acotor消息,所以特性标签
ActorMessageHandler
表示这个处理是普通Actor处理 - 因为是带回复的Actor消息,所以处理继承于AMActorRPCHander,且处理对象是当前GATE服务模块的scene实体。
- 来源是R2G_GetLoginKey,回复是G2R_GetLoginKey类型,且已经封装好reply回调,调用即可返回协议。
具体的处理内容:
- 生成一个随机key,用于返回给Realm,从而让Realm服返回给客户的
- 通过scene挂载的GateSessionKeyComponent组件注册这个key与对应的account,便于下次客户的用这个key进行Gate登录认证
- 将key,与当前scene实体的id发回给Realm服进行处理。
自此,一条简单的内网Actor协议通信已经完毕。
二、普通Actor相关代码解析
1.ActorMessageSenderComponent类
在Game.Scene.AddComponent<ActorMessageSenderComponent>();
中进行实例化,用于服务器内部发送普通Actor消息。
public static async ETTask<IActorResponse> Call(
this ActorMessageSenderComponent self,
long actorId,
IActorRequest request,
bool needException = true
)
{
request.RpcId = self.GetRpcId();
if (actorId == 0)
{
throw new Exception($"actor id is 0: {request}");
}
(ushort _, MemoryStream stream) = MessageSerializeHelper.MessageToStream(0, request);
return await self.Call(actorId, request.RpcId, stream, needException);
}
调用带回复的普通actor协议就用上述方式,会先生成对应的RpcId,用于在接受消息时,通过这个RpcId找到对应的TCS进行与Session发送带回复消息类似,利用ETTask进行异步处理。
如果是只发送普通Actor消息不带回复的可用:
public static void Send(this ActorMessageSenderComponent self, long actorId, IMessage message)
{
if (actorId == 0)
{
throw new Exception($"actor id is 0: {message}");
}
ProcessActorId processActorId = new ProcessActorId(actorId);
Session session = NetInnerComponent.Instance.Get(processActorId.Process);
session.Send(processActorId.ActorId, message);
}
public static void Send(this ActorMessageSenderComponent self, long actorId, MemoryStream memoryStream)
{
if (actorId == 0)
{
throw new Exception($"actor id is 0: {memoryStream.ToActorMessage()}");
}
ProcessActorId processActorId = new ProcessActorId(actorId);
Session session = NetInnerComponent.Instance.Get(processActorId.Process);
session.Send(processActorId.ActorId, memoryStream);
}
内部代码也十分简单,就是利用对应的actorId,找到对应的session(如果没有就新建一个对应的),进而使用底层封装好的socket一系列进行发送协议。
2.InnerMessageDispatcher收到Actor返回消息处理
在这个类中相关处理代码
case IActorResponse iActorResponse:
{
InstanceIdStruct instanceIdStruct = new InstanceIdStruct(actorId);
instanceIdStruct.Process = Game.Options.Process;
long realActorId = instanceIdStruct.ToLong();
InnerMessageDispatcherHelper.HandleIActorResponse(opcode, realActorId, iActorResponse);
return;
}
如果有数据从内网发送过来,经过InnerMessageDispatcher处理,因为属于IActorResponse消息,所以流转到InnerMessageDispatcherHelper类进行下一步处理:
public static void HandleIActorResponse(ushort opcode, long actorId, IActorResponse iActorResponse)
{
ActorMessageSenderComponent.Instance.RunMessage(actorId, iActorResponse);
}
内部就是直接调用ActorMessageSenderComponent的RunMessage进行处理,如下:
public static void RunMessage(this ActorMessageSenderComponent self, long actorId, IActorResponse response)
{
ActorMessageSender actorMessageSender;
if (!self.requestCallback.TryGetValue(response.RpcId, out actorMessageSender))
{
return;
}
self.requestCallback.Remove(response.RpcId);
Run(actorMessageSender, response);
}
public static void Run(ActorMessageSender self, IActorResponse response)
{
if (response.Error == ErrorCode.ERR_ActorTimeout)
{
self.Tcs.SetException(new Exception($"Rpc error: request, 注意Actor消息超时,请注意查看是否死锁或者没有reply: actorId: {self.ActorId} {self.MemoryStream.ToActorMessage()}, response: {response}"));
return;
}
if (self.NeedException && ErrorCode.IsRpcNeedThrowException(response.Error))
{
self.Tcs.SetException(new Exception($"Rpc error: actorId: {self.ActorId} request: {self.MemoryStream.ToActorMessage()}, response: {response}"));
return;
}
self.Tcs.SetResult(response);
}
可以看到处理的方式就是通过rpcId找到之前调用call时,注册的绑定ActorMessageSender类实例。ActorMessageSender类本身没什么方法,就是将call发送时的一些绑定,比如:actorId,数据流,ETTASK的Tcs,除了Tcs用于使用异步外,其他几个字段主要用于报错与超时处理。
public ActorMessageSender(long actorId, MemoryStream memoryStream, ETTask<IActorResponse> tcs, bool needException)
{
this.ActorId = actorId;
this.MemoryStream = memoryStream;
this.CreateTime = TimeHelper.ServerNow();
this.Tcs = tcs;
this.NeedException = needException;
}
到此一条普通actor消息的流转内部代码就全部简易过了一遍。由于ET6.0内部,底层通信的机制(session,service,channel,socekt等)都已经封装得相当完美,所以越到上层代码是越简单的。
总结
本篇主要记录,一条普通的内网Actor协议如何进行流转与处理。只从使用角度来看的话,只需要定义好协议体结构,协议处理类继承于AMActorRpcHandler或AMActorHandler,通过ActorMessageSenderComponent发送即可。
具体发送的内部代码也相对简单,且ET6.0已经中已经封装好多数协议,并有相关事例代码可以参考,且已经将普通协议,普通actor协议,以及定位actor协议(下一篇进行学习)都封装隔离区分了,学习与使用起来也是相当方便(当然需要一定的基础)。
下一篇主要学习定位actor协议的处理(即ActorLocation类消息),此类消息处理是专门用于处理那种会穿越进程的实体进行通信的一种技术,其原理就是在普通actor机制上又封装一层查询机制,待下一篇进行学习。