任务20:在线状态与登录时将其它地方还没退出的账号踢下线
大家把之前的课时内容重新捋一下前面的消息指令与消息体文件:
测试向服务端发送一条消息
登录、注册需要发送的消息
获取用户信息发送的消息
设置更多用户信息发送的消息
在HotfixMassage.proto文件中的定义的内容,确认生成了正确的HotfixMessage.cs,HotfixOpcode.cs
本节功能的实现,不需要在前端开发什么,打开服务端项目开始今天的开发。
将匹配组件OnlineComponent加到服务端起动类中
\Server\App\Program.cs
//斗地主服务端组件 Game.Scene.AddComponent<OnlineComponent>();
登录验证账号+密码通过后,就要先发起将此账号可能在别处已有在线踢下线
登录验证在 \Server\Hotfix\Landlords\Handler\Realm\A0002_LoginHandler.cs
需要在验证过用户名和密码后就调用KickOutPlayer()方法
//验证请求的账号和密码 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]; await RealmHelper.KickOutPlayer(account.Id);
验证是在realm上的,所以是在RealmHelper中创建KickOutPlayer()方法
发起踢已有在线的用户下线要在请求网关GetLoginKey,也就是A0006_GetLoginKey_G2R之前,不然如果他处有此用户还在线,就会提示重复登录。用户就无法完成新的登录。
我们理一下啊,要完成踢用户下线需要:
Realm服务的OnlineComponent移除用户
Gate服务移除session与user的绑定
UserComponent中移除用户
NetOuterComponent断开连接
在RealmHelper.cs中创建KickOutPlayer方法
Game.Scene上有OnlineComponent组件,所以通过OnlineComponent来判断用户在线状态。
所以就在这个方法中,如果判断已在线,就向Gate发送请求移除网关上此用户。
//通知Gate服务器,调用Gate上A0007_KickOutPlayer的处理Handler
await userGateSession.Call(new A0007_KickOutPlayer_R2G() { UserID = userId });
//判断用户是否在线,在线则踢下线 int gateAppId = Game.Scene.GetComponent<OnlineComponent>().GetGateAppId(userId); if (gateAppId != 0) //如果玩家在线 则返回其所在的AppID
RealmHelper中KickOutPlayer方法完整的代码是:
\Server\Hotfix\Helper\RealmHelper.cs
/// <summary> /// 将已在线的玩家踢下线 /// </summary> /// <param name="userId"></param> /// <returns></returns> public static async Task KickOutPlayer(long userId) { //判断用户是否在线,在线则踢下线 int gateAppId = Game.Scene.GetComponent<OnlineComponent>().GetGateAppId(userId); if (gateAppId != 0) //如果玩家在线 则返回其所在的AppID { Log.Debug($"玩家{userId}已在线 将执行踢下线操作"); //获取此User所在Gate服务器 StartConfig userGateConfig = Game.Scene.GetComponent<StartConfigComponent>().Get(gateAppId); IPEndPoint userGateIPEndPoint = userGateConfig.GetComponent<InnerConfig>().IPEndPoint; Session userGateSession = Game.Scene.GetComponent<NetInnerComponent>().Get(userGateIPEndPoint); //获取此User其它客户端与网关连接session /* 这里是向此User其它客户端发送一条测试消息, */ /* 你可以在移除它处登录用户前向之前客户端发送一条通知下线的消息让其返回登录界面 */ User user = Game.Scene.GetComponent<UserComponent>().Get(userId); Session ClientSession = Game.Scene.GetComponent<NetOuterComponent>().Get(user.GateSessionID); ClientSession.Send(new G2C_TestHotfixMessage() { Info = "recv hotfix message success" }); //向客户端发送玩家下线消息 Log.Info("它处登录,原登录踢下线《===="); //...练习,自己实现通知客户端下线 //通知Gate服务器移除指定User将它处登录用户踢下线 await userGateSession.Call(new A0007_KickOutPlayer_R2G() { UserID = userId }); } }
Gate上A0007_KickOutPlayer的处理Handler
\Server\Hotfix\Landlords\Handler\Gate\A0007_KickOutPlayer.cs
using System; using System.Net; using ETModel; namespace ETHotfix { [MessageHandler(AppType.Gate)] public class A0007_KickOutPlayer : AMRpcHandler<A0007_KickOutPlayer_R2G, A0007_KickOutPlayer_G2R> { protected override async ETTask Run(Session session, A0007_KickOutPlayer_R2G request, A0007_KickOutPlayer_G2R response,Action reply) { try { //获取此UserID的网关session long sessionId = Game.Scene.GetComponent<UserComponent>().Get(request.UserID).GateSessionID; Session lastSession = Game.Scene.GetComponent<NetOuterComponent>().Get(sessionId); //移除session与user的绑定 lastSession.RemoveComponent<SessionUserComponent>(); reply(); await ETTask.CompletedTask; } catch (Exception e) { ReplyError(response, e, reply); } } } }
Remove 组件SessionUserComponent,就会触发Destroy的扩展方法,我们来添加他的扩展方法
\Server\Hotfix\Landlords\System\SessionUserComponentSystem.cs
using ETModel; using System.Net; namespace ETHotfix { [ObjectSystem] public class SessionUserComponentDestroySystem : DestroySystem<SessionUserComponent> { public override void Destroy(SessionUserComponent self) { try { //释放User对象时将User对象从管理组件中移除 Log.Info($"销毁User和Session{self.User.UserID}"); Game.Scene.GetComponent<UserComponent>().Remove(self.User.UserID); StartConfigComponent config = Game.Scene.GetComponent<StartConfigComponent>(); //向登录服务器发送玩家下线消息 IPEndPoint realmIPEndPoint = config.RealmConfig.GetComponent<InnerConfig>().IPEndPoint; Session realmSession = Game.Scene.GetComponent<NetInnerComponent>().Get(realmIPEndPoint); realmSession.Send(new A0005_PlayerOffline_G2R() { UserID = self.User.UserID }); //服务端主动断开客户端连接 Game.Scene.GetComponent<NetOuterComponent>().Remove(self.User.GateSessionID); //Log.Info($"将玩家{message.UserID}连接断开"); self.User.Dispose(); self.User = null; } catch (System.Exception e) { Log.Trace(e.ToString()); } } } }
已经踢下线,然后就是完成新的登录上线,需要:
Realm上更新OnlineComponent增加用户
Gate服务绑定session与user
UserComponent中增加用户
前端的请求已经建立NetOuterComponent连接无需处理
到A0003_LoginGateHanler方法中增加向Realm发送此用户上线消息
前面学过的登录网关的Handler,这里面向Realm发送A0004_PlayerOnline_G2R更新OnlineComponent用户在线状态。
\Server\Hotfix\Landlords\Handler\Gate\A0003_LoginGateHanler.cs
//将新上线的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); //构建realmSession通知Realm服务器 玩家已上线 StartConfigComponent config = Game.Scene.GetComponent<StartConfigComponent>(); IPEndPoint realmIPEndPoint = config.RealmConfig.GetComponent<InnerConfig>().IPEndPoint; Session realmSession = Game.Scene.GetComponent<NetInnerComponent>().Get(realmIPEndPoint); //2个参数 1:UserID 2:GateAppID realmSession.Send(new A0004_PlayerOnline_G2R() { UserID = user.UserID, GateAppID = config.StartConfig.AppId });
A0004_PlayerOnline与A0005_PlayerOffline两个Handler
\Server\Hotfix\Landlords\Handler\Realm\A0004_PlayerOnline.cs
using ETModel; using System; namespace ETHotfix { [MessageHandler(AppType.Realm)] public class A0004_PlayerOnline : AMHandler<A0004_PlayerOnline_G2R> { protected override async ETTask Run(Session session, A0004_PlayerOnline_G2R message) { OnlineComponent onlineComponent = Game.Scene.GetComponent<OnlineComponent>(); //检查玩家是否在线 如不在线则添加 if (onlineComponent.GetGateAppId(message.UserID) == 0) { onlineComponent.Add(message.UserID, message.GateAppID); } else { Log.Error("玩家已在线 Realm服务器收到重复上线请求的异常"); } } } }
\Server\Hotfix\Landlords\Handler\Realm\A0005_PlayerOffline.cs
using ETModel; using System; namespace ETHotfix { [MessageHandler(AppType.Realm)] public class A0005_PlayerOffline : AMHandler<A0005_PlayerOffline_G2R> { protected override async ETTask Run(Session session, A0005_PlayerOffline_G2R message) { //玩家下线 Game.Scene.GetComponent<OnlineComponent>().Remove(message.UserID); Log.Info($"玩家{message.UserID}下线"); } } }
本节的消息指令与消息体
\Proto\InnerMessage.proto 中增加
//向realm用户发送上线消息 message A0004_PlayerOnline_G2R // IMessage { int32 RpcId = 90; int64 UserID = 1; int32 GateAppID = 2; } //向realm用户发送下线消息 message A0005_PlayerOffline_G2R // IMessage { int32 RpcId = 90; int64 UserID = 1; } message A0007_KickOutPlayer_R2G // IRequest { int32 RpcId = 90; int64 UserID = 1; } message A0007_KickOutPlayer_G2R // IResponse { int32 RpcId = 90; int32 Error = 91; string Message = 92; }
在前端unity中生成一下消息指令与消息体,复制到服务端。
运行前后端,我们登录账号,就能看到服务端的输出内容
---------------------------------
延伸思考:
本节学的是服务端踢玩家下线,那么如果是用户主动退出登录呢,你会自己实现吗?
我们只需要增加一个前端向gate的请求Client_Logout ,消息是C_G对吧,Handler是不是跟A0007_KickOutPlayer类似呢?