首页   /   ET   /   ET_003,网络层设计

内容

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普通消息的发送