zoukankan      html  css  js  c++  java
  • ET游戏框架整理笔记3: 常用内置组件功能

    上一节说道 挂载组件的几个步骤

    1. 组件工厂创建组件

    2. 如果有泛型类以组件为参数 并且实现了IUpdateSystem接口的话会调用它的update方法

    如下面这个类

    [ObjectSystem]
    public class TimerComponentUpdateSystem : UpdateSystem<TimerComponent>
    {
         public override void Update(TimerComponent self)
         {
             self.Update();
         }
    }

    3. 组件添加到父组件字典中

    -------------------------------------------------------上一节分界线-------------------------------------------------------


    main函数中加载完dll就是 挂载这两个组件  OptionComponent   StartConfigComponent

    Options options = Game.Scene.AddComponent<OptionComponent, string[]>(args).Options;
    StartConfig startConfig = Game.Scene.AddComponent<StartConfigComponent, string, int>(options.Config, options.AppId).StartConfig;

    这两个组件干嘛用的  上一节有说道创建组件时候会判断 ,会把 dotnet后面的参数序列化到组件的options属性中

    image

    startconfig组件根据上面的文件路径加载配置 根据apptype初始化组件的各类配置文件 如下面这些

    public StartConfig StartConfig { get; private set; }

    public StartConfig DBConfig { get; private set; }

    public StartConfig RealmConfig { get; private set; }

    public StartConfig LocationConfig { get; private set; }

    public List<StartConfig> MapConfigs { get; private set; }

    public List<StartConfig> GateConfigs { get; private set; }


    消息分发组件 MessageDispatcherComponent

    [ObjectSystem]
    public class MessageDispatcherComponentAwakeSystem : AwakeSystem<MessageDispatcherComponent>
    {
         public override void Awake(MessageDispatcherComponent self)
         {
             self.Load();
         }
    }

    创建该组件时调用load方法

    /// <summary>
         /// 消息分发组件
         /// </summary>
         public static class MessageDispatcherComponentHelper
         {
             public static void Load(this MessageDispatcherComponent self)
             {
                 self.Handlers.Clear();

                AppType appType = StartConfigComponent.Instance.StartConfig.AppType;

                List<Type> types = Game.EventSystem.GetTypes(typeof(MessageHandlerAttribute));

                foreach (Type type in types)
                 {
                     object[] attrs = type.GetCustomAttributes(typeof(MessageHandlerAttribute), false);
                     if (attrs.Length == 0)
                     {
                         continue;
                     }

                    MessageHandlerAttribute messageHandlerAttribute = attrs[0] as MessageHandlerAttribute;
                     if (!messageHandlerAttribute.Type.Is(appType))
                     {
                         continue;
                     }

                    IMHandler iMHandler = Activator.CreateInstance(type) as IMHandler;
                     if (iMHandler == null)
                     {
                         Log.Error($"message handle {type.Name} 需要继承 IMHandler");
                         continue;
                     }

                    Type messageType = iMHandler.GetMessageType();
                     ushort opcode = Game.Scene.GetComponent<OpcodeTypeComponent>().GetOpcode(messageType);
                     if (opcode == 0)
                     {
                         Log.Error($"消息opcode为0: {messageType.Name}");
                         continue;
                     }
                     self.RegisterHandler(opcode, iMHandler);
                 }
             }

            public static void RegisterHandler(this MessageDispatcherComponent self, ushort opcode, IMHandler handler)
             {
                 if (!self.Handlers.ContainsKey(opcode))
                 {
                     self.Handlers.Add(opcode, new List<IMHandler>());
                 }
                 self.Handlers[opcode].Add(handler);
             }

           
             }
         }

    这个方法会从所有组件中找到 标记有MessageHandlerAttribute的类遍历 => 反射创建该类的实例A  => 获取该Message的类型B,如下图的R2C_Ping  => 把B的编码作为key 实例A作为value 缓存到  ActorMessageDispatcherComponent 组件的ActorMessageHandlers字典中

    如下面这个就是一个消息处理类

    [MessageHandler(AppType.AllServer)]
    public class C2R_PingHandler : AMRpcHandler<C2R_Ping, R2C_Ping>
    {
         protected override async ETTask Run(Session session, C2R_Ping request, R2C_Ping response, Action reply)
         {
             reply();
             await ETTask.CompletedTask;
         }
    }

    缓存的这些消息处理类在后面收发协议时候回用到



    内网通信组件 NetInnerComponent

    顾名思义 这个是内网通信用的 添加组件时候传入了内网的地址

    Game.Scene.AddComponent<NetInnerComponent, string>(innerConfig.Address);

    添加组件后会自动调用awake方法 看一下这个方法干了啥

    public static void Awake(this NetInnerComponent self, string address)
    {
         self.Awake(NetworkProtocol.TCP, address, Packet.PacketSizeLength4);
         self.MessagePacker = new MongoPacker();
         self.MessageDispatcher = new InnerMessageDispatcher();
         self.AppType = StartConfigComponent.Instance.StartConfig.AppType;
    }

    标黄的方法是父组件 NetworkComponent的方法  根据协议类型创建不同的通信类

    public void Awake(NetworkProtocol protocol, string address, int packetSize = Packet.PacketSizeLength2)
             {
                     IPEndPoint ipEndPoint;
                     switch (protocol)
                     {
                         case NetworkProtocol.KCP:
                             ipEndPoint = NetworkHelper.ToIPEndPoint(address);
                             this.Service = new KService(ipEndPoint, this.OnAccept) { Parent = this };
                             break;
                         case NetworkProtocol.TCP:
                             ipEndPoint = NetworkHelper.ToIPEndPoint(address);
                             this.Service = new TService(packetSize, ipEndPoint, this.OnAccept) { Parent = this };
                             break;
                         case NetworkProtocol.WebSocket:
                             string[] prefixs = address.Split(';');
                             this.Service = new WService(prefixs, this.OnAccept) { Parent = this };
                             break;
                     }
             }

    这里公司用的是websocket

    然后就是开启websocket监听 配置文件中InnerConfig地址 127.0.0.1:20002

    websocket通讯服务端大概这样的

    *******************websocket服务端****************************************

    第一步:创建HttpListener类,并启动监听:

    var listener = new HttpListener();
    listener.Prefixes.Add("http://10.10.13.140:8080/");
    listener.Start();
    第二步:等待连接

    var context = listener.GetContext();
    第三步:接收websocket

    var wsContext = await context.AcceptWebSocketAsync(null);
    var ws = wsContext.WebSocket;
    Console.WriteLine("WebSocket connect");
    第四步:开始异步接收数据

    //接收数据
    var wsdata = await ws.ReceiveAsync(abuf, cancel);
    Console.WriteLine(wsdata.Count);
    byte[] bRec = new byte[wsdata.Count];
    Array.Copy(buf, bRec, wsdata.Count);
    Console.WriteLine(Encoding.Default.GetString(bRec));
    第五步:释放资源

    //注意,使用完,记得释放,不然会有内存泄漏
    ws.Dispose();

    ET不一样的是在接收到websocket消息后 ,调用一个OnAccept方法 创建session组件 然后调用start方法

    public void OnAccept(AChannel channel)
    {
         Session session = ComponentFactory.CreateWithParent<Session, AChannel>(this, channel);
         this.sessions.Add(session.Id, session);
         session.Start();
    }

    start方法调用WChannel的StartRecv() StartSend()方法

    public async ETVoid StartRecv()
             {
                 if (this.IsDisposed)
                 {
                     return;
                 }

                try
                 {
                     while (true)
                     {
    #if SERVER
                         ValueWebSocketReceiveResult receiveResult;
    #else
                         WebSocketReceiveResult receiveResult;
    #endif
                         int receiveCount = 0;
                         do
                         {
    #if SERVER
                             receiveResult = await this.webSocket.ReceiveAsync(
                                 new Memory<byte>(this.recvStream.GetBuffer(), receiveCount, this.recvStream.Capacity - receiveCount),
                                 cancellationTokenSource.Token);
    #else
                             receiveResult = await this.webSocket.ReceiveAsync(
                                 new ArraySegment<byte>(this.recvStream.GetBuffer(), receiveCount, this.recvStream.Capacity - receiveCount),
                                 cancellationTokenSource.Token);
    #endif
                             if (this.IsDisposed)
                             {
                                 return;
                             }

                            receiveCount += receiveResult.Count;
                         }
                         while (!receiveResult.EndOfMessage);

                        if (receiveResult.MessageType == WebSocketMessageType.Close)
                         {
                             this.OnError(ErrorCode.ERR_WebsocketPeerReset);
                             return;
                         }

                        if (receiveResult.Count > ushort.MaxValue)
                         {
                             await this.webSocket.CloseAsync(WebSocketCloseStatus.MessageTooBig, $"message too big: {receiveResult.Count}",
                                 cancellationTokenSource.Token);
                             this.OnError(ErrorCode.ERR_WebsocketMessageTooBig);
                             return;
                         }

                        this.recvStream.SetLength(receiveResult.Count);
                         this.OnRead(this.recvStream);
                     }
                 }
                 catch (Exception e)
                 {
                     Log.Error(e);
                     this.OnError(ErrorCode.ERR_WebsocketRecvError);
                 }
             }

    从message字节流中接受消息 交由Session.OnRead方法处理  onread方法如下

    包的前2个字节是opcode编码 后面的是消息体

    private void Run(MemoryStream memoryStream)
             {
                 memoryStream.Seek(Packet.MessageIndex, SeekOrigin.Begin);
                 ushort opcode = BitConverter.ToUInt16(memoryStream.GetBuffer(), Packet.OpcodeIndex);
                
    #if !SERVER
                 if (OpcodeHelper.IsClientHotfixMessage(opcode))
                 {
                     this.GetComponent<SessionCallbackComponent>().MessageCallback.Invoke(this, opcode, memoryStream);
                     return;
                 }
    #endif
                
                 object message;
                 try
                 {
                     OpcodeTypeComponent opcodeTypeComponent = this.Network.Entity.GetComponent<OpcodeTypeComponent>();
                     object instance = opcodeTypeComponent.GetInstance(opcode);
                     message = this.Network.MessagePacker.DeserializeFrom(instance, memoryStream);
                    
                     if (OpcodeHelper.IsNeedDebugLogMessage(opcode))
                     {
                         Log.Msg(message);
                     }
                 }
                 catch (Exception e)
                 {
                     // 出现任何消息解析异常都要断开Session,防止客户端伪造消息
                     Log.Error($"opcode: {opcode} {this.Network.Count} {e}, ip: {this.RemoteAddress}");
                     this.Error = ErrorCode.ERR_PacketParserError;
                     this.Network.Remove(this.Id);
                     return;
                 }

                if (!(message is IResponse response))
                 {
                     this.Network.MessageDispatcher.Dispatch(this, opcode, message);
                     return;
                 }
                
                 Action<IResponse> action;
                 if (!this.requestCallback.TryGetValue(response.RpcId, out action))
                 {
                     throw new Exception($"not found rpc, response message: {StringHelper.MessageToStr(response)}");
                 }
                 this.requestCallback.Remove(response.RpcId);

                action(response);
             }


    如上代码所示, 解析出opcode后 从OpcodeTypeComponent组件中找到opcode对应的类型,把消息体反序列化成opcode对应的请求或者相应类

    如果非IResponse消息的话 会把消息转给InnerMessageDispatcher类进行处理

    public void Dispatch(Session session, ushort opcode, object message)
             {
                 // 收到actor消息,放入actor队列
                 switch (message)
                 {
                     case IActorRequest iActorRequest:
                     {
                         Entity entity = (Entity)Game.EventSystem.Get(iActorRequest.ActorId);
                         if (entity == null)
                         {
                             Log.Warning($"not found actor: {message}");
                             ActorResponse response = new ActorResponse
                             {
                                 Error = ErrorCode.ERR_NotFoundActor,
                                 RpcId = iActorRequest.RpcId
                             };
                             session.Reply(response);
                             return;
                         }
        
                         MailBoxComponent mailBoxComponent = entity.GetComponent<MailBoxComponent>();
                         if (mailBoxComponent == null)
                         {
                             ActorResponse response = new ActorResponse
                             {
                                 Error = ErrorCode.ERR_ActorNoMailBoxComponent,
                                 RpcId = iActorRequest.RpcId
                             };
                             session.Reply(response);
                             Log.Error($"actor not add MailBoxComponent: {entity.GetType().Name} {message}");
                             return;
                         }
                    
                         mailBoxComponent.Add(new ActorMessageInfo() { Session = session, Message = iActorRequest });
                         return;
                     }
                     case IActorMessage iactorMessage:
                     {
                         Entity entity = (Entity)Game.EventSystem.Get(iactorMessage.ActorId);
                         if (entity == null)
                         {
                             Log.Error($"not found actor: {message}");
                             return;
                         }
        
                         MailBoxComponent mailBoxComponent = entity.GetComponent<MailBoxComponent>();
                         if (mailBoxComponent == null)
                         {
                             Log.Error($"actor not add MailBoxComponent: {entity.GetType().Name} {message}");
                             return;
                         }
                    
                         mailBoxComponent.Add(new ActorMessageInfo() { Session = session, Message = iactorMessage });
                         return;
                     }
                     default:
                     {
                         Game.Scene.GetComponent<MessageDispatcherComponent>().Handle(session, new MessageInfo(opcode, message));
                         break;
                     }
                 }
             }

    如上代码 消息类型有2种  IActorRequest   IActorMessage

    然后根据ActorId获取消息处理类

    这个ActorId是啥时候加到eventsystem中的?

    Game.EventSystem.Get(iActorRequest.ActorId);

    然后交由MailBoxComponent.Add方法 把消息放到队列中

  • 相关阅读:
    设计工具
    makefile介绍1.0
    cpp命名空间
    第二课 生活智慧
    第一课 我想找到好工作,我想挣钱
    php CURL
    apache 改变文档根目录www的位置
    yii2 模块的创建及使用
    yii2 源码分析Action类分析 (六)
    yii2 源码分析 model类分析 (五)
  • 原文地址:https://www.cnblogs.com/xtxtx/p/11273840.html
Copyright © 2011-2022 走看看