# 网络层设计

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发送消息以及远程调用及其简单方便，做分布式开发就跟开发单机一样方便。



