Procházet zdrojové kódy

Create 网络层设计.md

tanghai před 8 roky
rodič
revize
eb64ede35b
1 změnil soubory, kde provedl 131 přidání a 0 odebrání
  1. 131 0
      Doc/网络层设计.md

+ 131 - 0
Doc/网络层设计.md

@@ -0,0 +1,131 @@
+# 网络层设计
+
+ET框架提供了一个比较强大的网络消息层,发送消息订阅消息都及其方便,非常清晰简单。
+
+#### 1.普通消息的发送
+主要有两个组件,NetOuterComponent处理客户端的连接,NetInnerComponent处理服务端内部的连接
+这两个组件可以根据地址获取连接,每个连接都封装成了一个Session对象,Session对象有两个方法用来发送消息:
+``` C#
+// 根据地址创建或者获取一个连接
+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请求:
+``` C#
+// 客户端发送帐号密码给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.普通消息订阅
+上面是发送消息,服务器怎么订阅处理某个消息呢?非常简洁:
+```C#
+// 处理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组件:
+```c#
+// 从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对象的
+```c#
+[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返回响应消息。
+```c#
+[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发送消息以及远程调用及其简单方便,做分布式开发就跟开发单机一样方便。
+
+
+