ET框架提供了一个比较强大的网络消息层,发送消息订阅消息都及其方便,非常清晰简单。
1.普通消息的发送
主要有两个组件,
NetOuterComponent处理客户端的连接,
NetInnerComponent处理服务端内部的连接
这两个组件可以根据地址获取连接,
每个连接都封装成了一个Session对象,Session对象有两个方法用来发送消息
// 根据地址创建或者获取一个连接 Session session = Game.Scene.GetComponent<NetInnerComponent>().Get(innerAddress); // 只发送不等待返回 session.Send(new R2G_GetLoginKey()); // 发送R2G_GetLoginKey消息,并且等待消息返回一个G2R_GetLoginKey消息 G2R_GetLoginKey g2RGetLoginKey = await session.Call<G2R_GetLoginKey>(new R2G_GetLoginKey() {Account = "zhangsan"}); Log.Debug("打印响应的消息内容: " + g2RGetLoginKey.ToJson())
由于C#强大的async await语法,ET框架发送rpc消息显得非常简洁,
发送前后逻辑得以连贯,不用拆成两段逻辑,
正因为这个特性,C#非常适合写分布式框架,因为其实分布式无非就是进程间网络消息的处理。
假如没有这个功能,
你想想,发送消息写在一个地方,你还得订阅一个返回消息处理,
两块代码就不连贯。更可怕的是连续多个rpc请求:
// 客户端发送帐号密码给login server验证,并且等待login响应消息返回,login会分配一个网关给客户端 R2C_Login r2CLogin = await session.Call<R2C_Login>(new C2R_Login() { Account = "a", Password = "b" }); // 客户端连接网关 Session gateSession = Game.Scene.GetComponent<NetOuterComponent>().Create(r2CLogin.Address); // 客户端发送消息给网关,等待网关验证返回 G2C_LoginGate g2CLoginGate = await gateSession.Call<G2C_LoginGate>(new C2G_LoginGate(r2CLogin.Key)); Log.Info("登陆gate成功!"); // 获取玩家的物品信息 G2C_Items items = await gateSession.Call<G2C_Items>(new C2G_Items());
可以看到登录完loginserver,立即登录gateserver,
登录完成后又查询了玩家的物品信息,整个流程看起来非常连贯,
假如没有async await,这段代码就得拆成至少4块放到4个函数中。
分布式服务器连续rpc调用非常多,没有async await这种协程的语法支持是不可想像的。
所以有人用nodejs,java写游戏服务器,我是无法理解的,写单服还可以,写分布式服务器,呵呵!
2.普通消息订阅
上面是发送消息,服务器怎么订阅处理某个消息呢?非常简洁:
// 处理login rpc消息,并且返回response [MessageHandler(AppType.Login)] public class C2R_LoginHandler : AMRpcHandler<C2R_Login, R2C_Login> { protected override async void Run(Session session, C2R_Login message, Action<R2C_Login> reply) { R2C_Login response = new R2C_Login(); try { Log.Debug(message.ToJson()); reply(response); } catch (Exception e) { ReplyError(response, e, reply); } } }
rpc消息只需要在hotfix dll中加个类,类继承于AMRpcHandler,实现虚方法即可,
ET使用了声明式订阅消息的手法,一个rpc消息处理类,
只需要加上MessageHandlerAttribute就可以自动被框架发现并且注册到框架中,
并不需要手动用函数去注册。
上面这个类MessageHandlerAttribute设置了AppType.Login,
这个参数表示只有Login服务器才会注册这个rpc处理类。是不是非常简单呢?
同样的,注册非rpc消息,只需要添加一个类继承于AMHandler即可。
整个消息处理类不会包含任何状态,所以是可以热更新的。
3.actor消息发送
ET框架还提供了类似Erlang语言的分布式消息机制,
不管对象在任何进程,只需要挂载ActorComponent组件,
任何进程都可以拿着这个对象的id,向这个对象发送消息,
消息会发送到该对象所在的进程并且交给该对象处理。
发送Actor消息与普通消息不同, 要发送actor消息,server必须挂上ActorProxyComponent组件:
// 从ActorProxyComponent组件中获取actorproxy ActorProxy actorProxy = Game.Scene.GetComponent<ActorProxyComponent>().Get(id); // 向对象发送消息 actorProxy.Send(new Actor_Test()); // 向对象发送rpc消息 ActorRpc_TestResponse response = await actorProxy.Call<ActorRpc_TestResponse>(ActorRpc_TestRequest());
4.actor订阅处理
订阅actor消息与普通消息类似,只需要继承AMActorHandler,
并且加上ActorMessageHandler的标签。
有点不同的是AMActorHandler需要提供Actor的类型,
例如下面这个actor消息它是发给Player对象的
[ActorMessageHandler(AppType.Map)] public class Actor_TestHandler : AMActorHandler<Player, Actor_Test> { protected override async Task<bool> Run(Player player, Actor_Test message) { Log.Debug(message.Info); player.GetComponent<UnitGateComponent>().GetActorProxy().Send(message); return true; } }
同样订阅ActorRpc消息,需要继承AMActorRpcHandler,同样使用reply返回响应消息。
[ActorMessageHandler(AppType.Map)] public class ActorRpc_TestRequestHandler : AMActorRpcHandler<Player, ActorRpc_TestRequest, ActorRpc_TestResponse> { protected override async Task<bool> Run(Player entity, ActorRpc_TestRequest message, Action<ActorRpc_TestResponse> reply) { reply(new ActorRpc_TestResponse() {response = "response actor rpc"}); return true; } }
5.rpc消息的异常处理
ET框架消息层提供了强大的异常处理机制,所有rpc响应消息都继承与AResponse,AResponse带有error跟错误信息,
public abstract class AResponse: AMessage { public uint RpcId; public int Error = 0; public string Message = ""; }
可以捕获RpcException异常,通过ErrorCode做不同的异常处理,比方说客户端登录:
try { R2C_Login r2CLogin = await session.Call<R2C_Login>(new C2R_Login() { Account = "a", Password = "b" }); } catch (RpcException e) { if (e.Error == ErrorCode.ERR_AccountNotFound) { Log.Debug("帐号不存在"); return; } if (e.Error == ErrorCode.PasswordError;) { Log.Debug("密码错误"); return; } }
ET框架最方便的是异常信息会跨进程传递,
比如,A进程向B进程发起了Rpc请求,B进程在响应之前需要请求C,C进程响应B之前需要请求D,
结果,D进程在处理过程中发生了一个异常,这个异常会从D->C->B->A,
A进程在try catch中捕获了这个异常,这个异常会带有BCD整个过程的堆栈信息,
查分布式异常bug会变得非常简单。
总结
本文详细介绍了ET框架的网络层使用,
ET框架提供了非常完善的分布式网络层,强大的分布式异常处理机制。
因为协程的使用,ET发送消息以及远程调用及其简单方便,
做分布式开发就跟开发单机一样方便。
源文:https://github.com/egametang/ET/blob/master/Doc/%E7%BD%91%E7%BB%9C%E5%B1%82%E8%AE%BE%E8%AE%A1.md#1普通消息的发送