ET5.0 demo中添加心跳功能
一、服务端
1:添加文件夹,在Model/Module文件夹下新建文件夹 Hearbeat
2:添加心跳配置文件 SessionHeartbeatComponentAwakeSystem
namespace ETModel { [ObjectSystem] public class SessionHeartbeatComponentAwakeSystem : AwakeSystem<SessionHeartbeatComponent> { public override void Awake(SessionHeartbeatComponent self) { HeartbeatMgrComponent.Ins.AddSessionHeartbeat(self); } } public class SessionHeartbeatComponent: Component { public const int DestroySeesiontTime = 40;//多长 时间 没收到客户端消息 就直接销毁 public const int DestroySeesiontSecondTotalNum = 10;//一秒内收到多少条消息 就直接销毁 public int SecondTotalMessageNum = 0;//一秒内 累计收到的消息条数 public int UpReceiveMessageDistance = 0;//距离上次收到消息 有多长时间 //销毁Session public void DisposeSession() { Entity.Dispose(); } public override void Dispose() { HeartbeatMgrComponent.Ins.RemoveSessionHeartbeat(InstanceId);//必须在 base.Dispose(); 前面调 因为 Dispose会吧id置为0 base.Dispose(); SecondTotalMessageNum = 0;//一秒内 累计收到的消息条数 UpReceiveMessageDistance = 0;//距离上次收到消息 有多长时间 } } }
3、添加心跳检测组件 HeartbeatMgrComponentAwakeSystem
using System.Collections.Generic; namespace ETModel { [ObjectSystem] public class HeartbeatMgrComponentAwakeSystem: AwakeSystem<HeartbeatMgrComponent> { // public static List<SessionHeartbeatComponent> _DestroyHeartbeatComponents = new List<SessionHeartbeatComponent>(); public override void Awake(HeartbeatMgrComponent self) { HeartbeatMgrComponent.Ins = self; self.Awake(); } } public class HeartbeatMgrComponent: Component { public List<long> SessionHeartbeatIds = new List<long>(); public Dictionary<long, SessionHeartbeatComponent> SessionHeartbeatDic = new Dictionary<long, SessionHeartbeatComponent>(); public static HeartbeatMgrComponent Ins { get; set; } public async void Awake() { while (true) { await Game.Scene.GetComponent<TimerComponent>().WaitAsync(1000); for (int i = 0; i < SessionHeartbeatIds.Count; i++) { //一秒内收到消息的数量 SessionHeartbeatDic[SessionHeartbeatIds[i]].SecondTotalMessageNum = 0; SessionHeartbeatDic[SessionHeartbeatIds[i]].UpReceiveMessageDistance++; if (SessionHeartbeatDic[SessionHeartbeatIds[i]].UpReceiveMessageDistance>= SessionHeartbeatComponent.DestroySeesiontTime) { SessionHeartbeatDic[SessionHeartbeatIds[i]].DisposeSession();//如果上次收到时间 过长 就直接销毁DisposeSession } } } } /// <summary> /// 添加SessionHeartbeat 组件 /// </summary> /// <param name="sessionHeartbeat"></param> public void AddSessionHeartbeat(SessionHeartbeatComponent sessionHeartbeat) { SessionHeartbeatDic[sessionHeartbeat.InstanceId] = sessionHeartbeat; SessionHeartbeatIds.Add(sessionHeartbeat.InstanceId); } /// <summary> /// 移除组件 /// </summary> /// <param name="id"></param> public void RemoveSessionHeartbeat(long id) { if (SessionHeartbeatDic.ContainsKey(id)) { SessionHeartbeatDic.Remove(id); SessionHeartbeatIds.Remove(id); } } } }
4、添加Session挂载心跳组件 SessionHeartbeatAwakeSystem
namespace ETModel { [ObjectSystem] public class SessionHeartbeatAwakeSystem:AwakeSystem<Session, AChannel> { public override void Awake(Session self, AChannel a) { if (self.Network.AppType==AppType.None)//不是任何APPType 就是客户端 { self.AddComponent<SessionHeartbeatComponent>(); } } } }
5、Program.cs 文件在 AppType.AllServer中挂载心跳组件
//与客户端有连接都要加 Game.Scene.AddComponent<HeartbeatMgrComponent>();//心跳管理组件 验证服 也是要加的
6、处理心跳消息,在OuterMessage.proto 添加
//心跳消息 message C2G_Heartbeat // IMessage { int32 RpcId = 90; }
在 OuterMessageDispatcher 文件中添加心跳逻辑处理
using ETModel; namespace ETHotfix { public class OuterMessageDispatcher: IMessageDispatcher { public void Dispatch(Session session, ushort opcode, object message) { DispatchAsync(session, opcode, message).Coroutine(); } public async ETVoid DispatchAsync(Session session, ushort opcode, object message) { /********* 添加心跳修改 **************/ SessionHeartbeatComponent sessionHeartbeatComponent = session.GetComponent<SessionHeartbeatComponent>(); if (sessionHeartbeatComponent == null) { Log.Info("---not heartbeat compinent---"); session.Dispose();//心跳组件 没有 直接销毁 return; } sessionHeartbeatComponent.UpReceiveMessageDistance = 0;//重置上次收到消息的时间 //如果收到 一秒收到的消息 大于规定的消息 就认定是DOSS攻击 直接销毁 if (++sessionHeartbeatComponent.SecondTotalMessageNum >= SessionHeartbeatComponent.DestroySeesiontSecondTotalNum) { //断开连接 sessionHeartbeatComponent.DisposeSession(); //直接封号 // UserHelp.StopSealOrRelieve(sessionUserComponent.UserId,true,"DOSS攻击封号"); //不要封号容易误封 return; } /********* 添加心跳修改 **************/ // 根据消息接口判断是不是Actor消息,不同的接口做不同的处理 switch (message) { case IActorLocationRequest actorLocationRequest: // gate session收到actor rpc消息,先向actor 发送rpc请求,再将请求结果返回客户端 { long unitId = session.GetComponent<SessionPlayerComponent>().Player.UnitId; ActorLocationSender actorLocationSender = await Game.Scene.GetComponent<ActorLocationSenderComponent>().Get(unitId); int rpcId = actorLocationRequest.RpcId; // 这里要保存客户端的rpcId long instanceId = session.InstanceId; IResponse response = await actorLocationSender.Call(actorLocationRequest); response.RpcId = rpcId; // session可能已经断开了,所以这里需要判断 if (session.InstanceId == instanceId) { session.Reply(response); } break; } case IActorLocationMessage actorLocationMessage: { long unitId = session.GetComponent<SessionPlayerComponent>().Player.UnitId; ActorLocationSender actorLocationSender = await Game.Scene.GetComponent<ActorLocationSenderComponent>().Get(unitId); actorLocationSender.Send(actorLocationMessage).Coroutine(); break; } case IActorRequest actorRequest: // 分发IActorRequest消息,目前没有用到,需要的自己添加 { break; } case IActorMessage actorMessage: // 分发IActorMessage消息,目前没有用到,需要的自己添加 { break; } case C2G_Heartbeat c2GHeartbeat: //单独处理心跳 { //心跳不做处理 Log.Info("--收到心跳--"); break; } default: { // 非Actor消息 Game.Scene.GetComponent<MessageDispatcherComponent>().Handle(session, new MessageInfo(opcode, message)); break; } } } } }
7、重新编译
二、客户端
1、在客户端 Assets/Hotfix/Module/Demo新建文件 HeartbeatComponent
using ETModel; namespace ETHotfix { [ObjectSystem] public class HeartbeatComponentAwakeSystem: AwakeSystem<HeartbeatComponent> { public override void Awake(HeartbeatComponent self) { self.Awake(); } public async void DetectionNetworkType(Session session) { // while (!session.IsDisposed) // { // await ETModel.Game.Scene.GetComponent<TimerComponent>().WaitAsync(1000); // //如果当前是无网络状态 发送连接失败的消息 // if (NetworkType.None == SdkMgr.Ins.GetNetworkInfo()) // { // session.session.Error = (int) SocketError.NetworkDown; // session.Dispose(); // } // } } } /// <summary> /// 心跳组件 /// </summary> public class HeartbeatComponent: Component { public static C2G_Heartbeat heartBeat = new C2G_Heartbeat(); public async void Awake() { //每间隔10秒发一条 心跳消息 // DetectionNetworkType(session);//检测网络连接状态 Log.Debug("--status:" + !IsDisposed); while (!IsDisposed) { await ETModel.Game.Scene.GetComponent<TimerComponent>().WaitAsync(10000); Log.Debug("--ping---" + ETModel.SessionComponent.Instance.Session.IsDisposed); if (ETModel.SessionComponent.Instance.Session.IsDisposed) { Log.Error("--服务器连接断开-"); } ETModel.SessionComponent.Instance.Session.Send(heartBeat); } } } }
运行:
1、重新编译服务器,并运行。不要移动小人,停止40秒(服务端配置的是40秒)左右,服务器会断开客户端。
2、修改客户端,挂载心跳组件发送心跳
我是在登录gate服务器成功时添加的组件,文件LoginHelper.cs中添加组件
//挂载心跳组建 Game.Scene.GetComponent<SessionComponent>().Session.AddComponent<HeartbeatComponent>();
3、再次运行,不会断开客户端。服务器每隔10秒收到心跳消息。
注意:这里只处理了服务端检测心跳,去掉无心跳的链接。没有处理客户端的连接状态。可以将心跳消息修改为 IRequest,通过Call方式发生心跳。
心跳组件参考: 五星麻将的心跳组件。