任务14:后端-注册登录处理Handler与消息
7,8,9,10知识量还是很多的,新手要删除代码多做几次。另外,思路上是有主干的。
第一个:主要就是请求,消息指令,消息类型,消息体,接收消息返回handler
第二个:构建需要的ECS对象(Entity,Component,System)
第三个:顺顺Map,Gate,Realm服务。
有了这个骨干思维,就是在各方法体里实现需要的功能了。
第一个小山头最高点胜利就在眼前了哦!加油!有了前面打好的基础,现在就轻松了。
前面服务端创建的组件,在Program.cs添加
E:\ETCoreLandlords\Landlords_Server02_03\Server\App\Program.cs
//数据库组件 Game.Scene.AddComponent<DBComponent>(); Game.Scene.AddComponent<DBProxyComponent>(); Game.Scene.AddComponent<UserComponent>(); Game.Scene.AddComponent<SessionKeyComponent>(); Game.Scene.AddComponent<SessionUserComponent>();
在服务端添加两个Helper类 GateHelper.cs,RealmHelper.cs 看其它对象中怎么用的就明白,不多讲。\Server\Hotfix\Helper\
using ETModel; using System.Net; namespace ETHotfix { public static class GateHelper { /// <summary> /// 验证Session是否绑定了玩家 /// </summary> /// <param name="session"></param> /// <returns></returns> public static bool SignSession(Session session) { SessionUserComponent sessionUser = session.GetComponent<SessionUserComponent>(); if (sessionUser == null || Game.Scene.GetComponent<UserComponent>().Get(sessionUser.User.UserID) == null) { return false; } return true; } } }
using ETModel; using System.Net; using System.Threading.Tasks; namespace ETHotfix { public static class RealmHelper { private static int value; /// <summary> /// 随机生成区号 1~ /// </summary> public static long GenerateId() { //随机获得GateId 1~2 int randomGateAppId = RandomHelper.RandomNumber(0, StartConfigComponent.Instance.GateConfigs.Count) + 1; long time = TimeHelper.ClientNowSeconds(); //1540 2822 75 时间为10位数 //区号取第11位数 return (randomGateAppId *100000000000 + time + ++value); } /// <summary> /// 生成指定大区的账号 参数为1以上的整数 /// </summary> /// <param name="GateAppId"></param> /// <returns></returns> public static long GenerateId(int GateAppId) { long time = TimeHelper.ClientNowSeconds(); //1540 2822 75 时间为10位数 //区号取第11位数 return (GateAppId * 100000000000 + time + ++value); } /// <summary> /// 查询账号所在大区 参数为1以上的整数 /// </summary> public static int GetGateAppIdFromUserId(long userID) { return (int)(userID/100000000000); } } }
在服务端添加与前端对应的消息指令,消息类型,消息体
前后端的消息指令、消息类型、消息体就是一样的。(上节你应该已经复制一份到服务端了吧)
如果服务端收到请求后出现 0001 消息没有处理:xxx数字编号 的报错,通常是前端请求与后端接收请求指令编号不一致。
添加定义错误顾类型ErrorCode
\Server\ET.Core\Module\Message\ErrorCode.cs
//自定义错误 public const int ERR_AccountAlreadyRegisted = 300001; public const int ERR_RepeatedAccountExist = 300002; public const int ERR_UserNotOnline = 300003; public const int ERR_CreateNewCharacter = 300007; public const int ERR_Success = 0;
在服务端添加与前端对应的请求处理Handler
对应前端请求A0001_RegisterHandler
using System; using System.Net; using ETModel; using System.Collections.Generic; using MongoDB.Bson; namespace ETHotfix { [MessageHandler(AppType.Realm)] public class A0001_RegisterHandler : AMRpcHandler<A0001_Register_C2R, A0001_Register_R2C> { protected override async ETTask Run(Session session, A0001_Register_C2R request,A0001_Register_R2C response, Action reply) { try { DBProxyComponent dbProxy = Game.Scene.GetComponent<DBProxyComponent>(); //验证假定的账号和密码 List<ComponentWithId> result = await dbProxy.Query<AccountInfo>($"{{Account:'{request.Account}'}}"); if (result.Count == 1) { response.Error = ErrorCode.ERR_AccountAlreadyRegisted; reply(); return; } else if (result.Count > 1) { response.Error = ErrorCode.ERR_RepeatedAccountExist; Log.Error("出现重复账号:" + request.Account); reply(); return; } //生成玩家帐号 这里随机生成区号 AccountInfo newAccount = ComponentFactory.CreateWithId<AccountInfo>(RealmHelper.GenerateId()); newAccount.Account = request.Account; newAccount.Password = request.Password; await dbProxy.Save(newAccount); //生成玩家的用户信息 用户名在消息中提供 UserInfo newUser = ComponentFactory.CreateWithId<UserInfo, string>(newAccount.Id, request.Account); await dbProxy.Save(newUser); reply(); await ETTask.CompletedTask; } catch (Exception e) { ReplyError(response, e, reply); } } } }
介绍下MongoDB组件的基础使用
框架的DBProxyComponent 提供了方法,Query<AccountInfo>(),Save()
可以找些MongoDB C#相关的资料多练习一下理解熟悉,这里只是简单介绍。

我们前面创建的每个数据实体类对应一个Mongo数据Collection,通过Studio 3T,可以直观的看到。
Query<查询目标>(查询条件)
Save(保存目标Collection对应的实例)
List<ComponentWithId> result = await dbProxy.Query<AccountInfo>($"{{Account:'{request.Account}'}}"); //生成玩家帐号 这里随机生成区号 AccountInfo newAccount = ComponentFactory.CreateWithId<AccountInfo>(RealmHelper.GenerateId()); newAccount.Account = request.Account; newAccount.Password = request.Password; await dbProxy.Save(newAccount); //生成玩家的用户信息 用户名在消息中提供 UserInfo newUser = ComponentFactory.CreateWithId<UserInfo, string>(newAccount.Id, request.Account); await dbProxy.Save(newUser);
大家经过观察可以发现,其实数据实体类的属性,对应了数据表中的字段!是不是开始有点明白了呢?
对应前端请求A0002_Login_C2R
A0002_LoginHandler.cs \Server\Hotfix\Landlords\Handler\Realm\A0002_LoginHandler.cs
查询数据验证账号,再向Gate请求A0006_GetLoginKey_R2G获得GateLoginKey
using System; using System.Net; using ETModel; using System.Collections.Generic; using MongoDB.Bson; namespace ETHotfix { [MessageHandler(AppType.Realm)] public class A0002_LoginHandler : AMRpcHandler<A0002_Login_C2R, A0002_Login_R2C> { protected override async ETTask Run(Session session, A0002_Login_C2R request,A0002_Login_R2C response,Action reply) { try { DBProxyComponent dbProxy = Game.Scene.GetComponent<DBProxyComponent>(); //验证提交来的的账号和密码 List<ComponentWithId> result = await dbProxy.Query<AccountInfo>($"{{Account:'{request.Account}',Password:'{request.Password}'}}"); if (result.Count != 1) { response.Error = ErrorCode.ERR_AccountOrPasswordError; reply(); return; } AccountInfo account = (AccountInfo)result[0]; int GateAppId; StartConfig config; //获取账号所在区服的AppId 索取登陆Key if (StartConfigComponent.Instance.GateConfigs.Count ==1) { //只有一个Gate服务器时当作AllServer配置处理 config = StartConfigComponent.Instance.StartConfig; } else { //有多个Gate服务器时当作分布式配置处理 GateAppId = RealmHelper.GetGateAppIdFromUserId(account.Id); config = StartConfigComponent.Instance.GateConfigs[GateAppId - 1]; } IPEndPoint innerAddress = config.GetComponent<InnerConfig>().IPEndPoint; Session gateSession = Game.Scene.GetComponent<NetInnerComponent>().Get(innerAddress); string outerAddress = config.GetComponent<OuterConfig>().Address2; A0006_GetLoginKey_G2R g2RGetLoginKey = (A0006_GetLoginKey_G2R)await gateSession.Call(new A0006_GetLoginKey_R2G() { UserID = account.Id }); response.GateAddress = outerAddress; response.GateLoginKey = g2RGetLoginKey.GateLoginKey; reply(); } catch (Exception e) { ReplyError(response, e, reply); } } } }
对应前端请求A0003_LoginGate_C2G
A0003_LoginGateHanler.cs \Server\Hotfix\Landlords\Handler\Gate\A0003_LoginGateHanler.cs
完成登录验证,创建User对象,将新上线的User添加到容器中
session挂SessionUserComponent,体会下是不是就将此User绑定到了session上
session与user挂MailBoxComponent,就可以通过MailBoxComponent进行actor通信
using System; using ETModel; using System.Net; namespace ETHotfix { [MessageHandler(AppType.Gate)] public class A0003_LoginGateHanler : AMRpcHandler<A0003_LoginGate_C2G, A0003_LoginGate_G2C> { protected override async ETTask Run(Session session, A0003_LoginGate_C2G request,A0003_LoginGate_G2C response,Action reply) { try { SessionKeyComponent SessionKeyComponent = Game.Scene.GetComponent<SessionKeyComponent>(); //获取玩家的永久Id long gateUserID = SessionKeyComponent.Get(request.GateLoginKey); //验证登录Key是否正确 if (gateUserID == 0) { response.Error = ErrorCode.ERR_ConnectGateKeyError; //客户端提示:连接网关服务器超时 reply(); return; } //Key过期 SessionKeyComponent.Remove(request.GateLoginKey); //gateUserID传参创建User User user = ComponentFactory.Create<User, long>(gateUserID); //将新上线的User添加到UserComponent容器中 Game.Scene.GetComponent<UserComponent>().Add(user); user.AddComponent<MailBoxComponent>(); //session挂SessionUser组件,user绑定到session上 //session挂MailBox组件可以通过MailBox进行actor通信 session.AddComponent<SessionUserComponent>().User = user; session.AddComponent<MailBoxComponent, string>(MailboxType.GateSession); StartConfigComponent config = Game.Scene.GetComponent<StartConfigComponent>(); //构建realmSession通知Realm服务器 玩家已上线 //... //设置User的参数 user.GateAppID = config.StartConfig.AppId; user.GateSessionID = session.InstanceId; user.ActorID = 0; //回复客户端 response.UserID = user.UserID; reply(); await ETTask.CompletedTask; } catch (Exception e) { ReplyError(response, e, reply); } } } }
特别补充:上面LoginGateHandler中有这样两处
- user.AddComponent<MailBoxComponent>();
- session.AddComponent<MailBoxComponent, string>(MailboxType.GateSession);
这是将Gate上的session,user通过挂MailBoxComponent进行actor通信,后面还会遇到给Map上的Gamer挂
MailBoxComponent进行actor通信:
newgamer.AddComponent<MailBoxComponent>().AddLocation();这里大家特注意一下要留下深一点的印象!!!
添加 MailBoxComponent时,有带MailboxType.GateSession参数和不带两种方式的,这是有重要用意和区别的,以后我们会讲到。
在A0002_LoginHandler.cs中向Gate请求获得GateLoginKey
A0006_GetLoginKey.cs \Server\Hotfix\Landlords\Handler\Gate\A0006_GetLoginKey.cs
using ETModel; using System; namespace ETHotfix { [MessageHandler(AppType.Gate)] public class A0006_GetLoginKey : AMRpcHandler<A0006_GetLoginKey_R2G, A0006_GetLoginKey_G2R> { protected override async ETTask Run(Session session, A0006_GetLoginKey_R2G request, A0006_GetLoginKey_G2R response,Action reply) { try { long key = RandomHelper.RandInt64(); Game.Scene.GetComponent<SessionKeyComponent>().Add(key, request.UserID); response.GateLoginKey = key; reply(); await ETTask.CompletedTask; } catch (Exception e) { ReplyError(response, e, reply); } } } }
GateLoginKey请求对应的消息定义
InnerMessage.proto 中增加GetLogKey请求的消息指令与类型
\Proto\InnerMessage.proto
/// <summary> /// 斗地主内网消息 /// </summary> message A0006_GetLoginKey_R2G // IRequest { int32 RpcId = 90; int64 UserID = 1; } message A0006_GetLoginKey_G2R // IResponse { int32 RpcId = 90; int32 Error = 91; string Message = 92; int64 GateLoginKey = 1; }
更新 .proto 文件后,到unity中找到菜单点Tools>Ptoto2CS工具,确认下重新生成的消息体类与指令脚本。(同样复制改变的消息指令与消息类到服务端!)
课时有调整,可能你错过了重要的这课,不知道定义和自动生成消息体,消息指令,请前往这课学习 定义消息体字段,用protobuf工具生成消息体
前后端都运行起来吧,先注册一个账号,然后登录即可看到效果。
登录成功后只是获得一个返回,加了一个空界面。
下节我们继续讲如何实现进入游戏大厅。


注册,登录成功界面上都有红字提示,注意看红字,并没有做登录注册界面的切换效果。

浙公网安备 33010602011771号