ET5.0-添加心跳功能

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;//距离上次收到消息 有多长时间
        }
    }
}
View Code

 

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);
            }
        }
        
    }

}
View Code

 

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>();
            }
        }
    }
}
View Code

 

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;
                }
            }
        }
    }
}
View Code

 

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);
            }
        }
    }
}
View Code

 

运行:

1、重新编译服务器,并运行。不要移动小人,停止40秒(服务端配置的是40秒)左右,服务器会断开客户端。

 

 

2、修改客户端,挂载心跳组件发送心跳

我是在登录gate服务器成功时添加的组件,文件LoginHelper.cs中添加组件

   //挂载心跳组建
                Game.Scene.GetComponent<SessionComponent>().Session.AddComponent<HeartbeatComponent>();

 

 

3、再次运行,不会断开客户端。服务器每隔10秒收到心跳消息。

 

 

注意:这里只处理了服务端检测心跳,去掉无心跳的链接。没有处理客户端的连接状态。可以将心跳消息修改为  IRequest,通过Call方式发生心跳。

 

 

 

心跳组件参考: 五星麻将的心跳组件。

 

posted @ 2021-02-26 11:31  Joy_CShow  阅读(419)  评论(0编辑  收藏  举报