BeetleX实现MessagePack和Protobuf消息控制器调用websocket服务详解

        最近有用户问如何使用BeetleX封装一个基于Protobuf格式的websocket服务并支持控制器调用;其实BeetleX.FastHttpApi是支持Websocket服务和自定义数据格式的,但需要对组件有一定了解的情况才能进行扩展;接下来通过封装一个支持Protobuf和MessagePack的websocket服务来介绍BeetleX.FastHttpApi的相关功能扩展和应用。

格式封装

        首先需要一个特性来标记消息类型在数据中用怎样的数据来标记

[AttributeUsage(AttributeTargets.Class)]
    public class BinaryTypeAttribute : Attribute
    {
        public BinaryTypeAttribute(int id)
        {
            this.ID = id;
        }
        public int ID { get; set; }
    }

可以通过一个Int类型来定义数值与消息类的关系。然后再定义一个特性用于描述消息对应的方法控制器类

[AttributeUsage(AttributeTargets.Class)]
    public class MessageControllerAttribute : Attribute
    {


    }

接下来就可以封装对应Protobuf的数据转换类,为了转换方便就直接使用Protobuf.Net这个组件了

public class BinaryDataFactory
    {


        //数值与类型的关系
        private Dictionary<int, Type> mMessageTypes = new Dictionary<int, Type>();
        //类型与数值的关系
        private Dictionary<Type, int> mMessageIDs = new Dictionary<Type, int>();
        //定义消息与方法的关系
        private Dictionary<Type, ActionHandler> mActions = new Dictionary<Type, ActionHandler>();


        public void RegisterComponent<T>()
        {
            RegisterComponent(typeof(T));
        }


        public void RegisterComponent(Type type)
        {
            foreach (var item in type.Assembly.GetTypes())
            {
                BinaryTypeAttribute[] bta = (BinaryTypeAttribute[])item.GetCustomAttributes(typeof(BinaryTypeAttribute), false);
                if (bta != null && bta.Length > 0)
                {
                    mMessageTypes[bta[0].ID] = item;
                    mMessageIDs[item] = bta[0].ID;
                }
#if PROTOBUF_SERVER
                var mca = item.GetCustomAttribute<MessageControllerAttribute>(false);
                if (mca != null)
                {
                    var controller = Activator.CreateInstance(item);
                    foreach (MethodInfo method in item.GetMethods(BindingFlags.Public | BindingFlags.Instance))
                    {
                        var parameters = method.GetParameters();
                        if (parameters.Length == 2 && parameters[0].ParameterType == typeof(WebSocketReceiveArgs)) {
                            ActionHandler handler = new ActionHandler(controller, method);
                            mActions[parameters[1].ParameterType] = handler;
                        }
                    }
                }
#endif
            }
        }
        //序列化对象
        public ArraySegment<byte> Serializable(object data)
        {
            MemoryStream memory = new MemoryStream();
            var type = GetTypeID(data.GetType(), true);
            memory.Write(type);
            Serializer.Serialize(memory, data);
            return new ArraySegment<byte>(memory.GetBuffer(), 0, (int)memory.Length);
        }
        //反序列化对象
        public object Deserialize(byte[] data)
        {
            return Deserialize(new ArraySegment<byte>(data, 0, data.Length));
        }
        //反序列化对象
        public object Deserialize(ArraySegment<byte> data)
        {
            MemoryStream memory = new MemoryStream(data.Array, data.Offset, data.Count);
            byte[] id = new byte[4];
            memory.Read(id, 0, 4);
            Type type = GetMessageType(id, true);
            return Serializer.Deserialize(type, memory);
        }
        //获消息对应数值的存储数据
        public byte[] GetTypeID(Type type, bool littleEndian)
        {
            if (mMessageIDs.TryGetValue(type, out int value))
            {


                if (!littleEndian)
                {
                    value = BeetleX.Buffers.BitHelper.SwapInt32(value);
                }
                return BitConverter.GetBytes(value);


            }
            throw new Exception($"binary websocket {type} id not found!");
        }
        //根据数值获取类型
        public Type GetMessageType(int id)
        {
            mMessageTypes.TryGetValue(id, out Type result);
            return result;
        }
        //根据存储数获取类型
        public Type GetMessageType(byte[] data, bool littleEndian)
        {
            int value = BitConverter.ToInt32(data, 0);
            if (!littleEndian)
                value = BeetleX.Buffers.BitHelper.SwapInt32(value);
            return GetMessageType(value);
        }
        //根据消息获处理方法
        public ActionHandler GetHandler(object message)
        {
            mActions.TryGetValue(message.GetType(), out ActionHandler result);
            return result;
        }
    }


    public class ActionHandler
    {
        public ActionHandler(object controller, MethodInfo method)
        {
            Method = method;
            Controller = controller;
            IsVoid = method.ReturnType == typeof(void);
            IsTaskResult = method.ReturnType.BaseType == typeof(Task);
            if (IsTaskResult && method.ReturnType.IsGenericType)
            {
                HasTaskResultData = true;
                mGetPropertyInfo = method.ReturnType.GetProperty("Result", BindingFlags.Public | BindingFlags.Instance);
            }
        }
        private PropertyInfo mGetPropertyInfo;
        public MethodInfo Method { get; private set; }
        public bool IsTaskResult { get; set; }
        public bool HasTaskResultData { get; set; }
        public bool IsVoid { get; private set; }
        public object Controller { get; private set; }
        public object GetTaskResult(Task task)
        {
            return mGetPropertyInfo.GetValue(task);
        }
        public object Execute(params object[] data)
        {
            return Method.Invoke(Controller, data);
        }
    }

协议转换

        Protobuf的数据格式处理完成后就要针对BeetleX.FastHttpApi构建一个相应数据转换器,这个转换器实现也是非常简单的。

public class ProtobufFrameSerializer : IDataFrameSerializer
    {
        public static BinaryDataFactory BinaryDataFactory { get; set; } = new BinaryDataFactory();
        public object FrameDeserialize(DataFrame data, PipeStream stream)
        {
            var buffers = new byte[data.Length];
            stream.Read(buffers, 0, buffers.Length);
            return BinaryDataFactory.Deserialize(buffers);
        }
        public void FrameRecovery(byte[] buffer)
        {


        }
        public ArraySegment<byte> FrameSerialize(DataFrame packet, object body)
        {
            return BinaryDataFactory.Serializable(body);
        }
    }

实现IDataFrameSerializer接口的相关方法即可,接下来就针对HttpApiServer封装一些扩展方法用于注册对象和绑定Websocket数据处理事件。

public static class ProtobufExtensions
    {
        public static HttpApiServer RegisterProtobuf<T>(this HttpApiServer server)
        {
            ProtobufFrameSerializer.BinaryDataFactory.RegisterComponent<T>();
            return server;
        }
        public static HttpApiServer UseProtobufController(this HttpApiServer server, Action<WebSocketReceiveArgs> handler = null)
        {
            server.WebSocketReceive = async (o, e) =>
            {
                try
                {
                    var msg = e.Frame.Body;
                    var action = ProtobufFrameSerializer.BinaryDataFactory.GetHandler(msg);
                    if (action != null)
                    {
                        if (!action.IsVoid)
                        {
                            if (action.IsTaskResult)
                            {


                                Task task = (Task)action.Execute(e, msg);
                                await task;
                                if (action.HasTaskResultData)
                                {
                                    var result = action.GetTaskResult(task);
                                    e.ResponseBinary(result);
                                }
                            }
                            else
                            {
                                var result = action.Execute(e, msg);
                                e.ResponseBinary(result);
                            }
                        }
                    }
                    else
                    {
                        handler?.Invoke(e);
                    }
                }
                catch (Exception e_)
                {
                    e.Request.Server.GetLog(BeetleX.EventArgs.LogType.Warring)
                    ?.Log(BeetleX.EventArgs.LogType.Error, e.Request.Session, $"Websocket packet process error {e_.Message}");
                }
            };
            return server;
        }
    }

服务使用

        所有相关功能都封装后就可以启动HttpApiServer并把相关功能设置使用,在使用之前需要引用BeetleX.FastHttpApi.Hosting和Protobuf.Net组件

[BinaryType(1)]
    [ProtoContract]
    public class User
    {
        [ProtoMember(1)]
        public string Name { get; set; }
        [ProtoMember(2)]
        public string Email { get; set; }
        [ProtoMember(3)]
        public DateTime ResultTime { get; set; }
    }
    [MessageController]
    public class Controller
    {
        public User Login(WebSocketReceiveArgs e, User user)
        {
            user.ResultTime = DateTime.Now;
            return user;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            BeetleX.FastHttpApi.Hosting.HttpServer server = new BeetleX.FastHttpApi.Hosting.HttpServer(80);
            server.Setting((service, options) =>
            {
                options.LogLevel = BeetleX.EventArgs.LogType.Trace;
                options.LogToConsole = true;
                options.ManageApiEnabled = false;
                options.WebSocketFrameSerializer = new ProtobufFrameSerializer();
            });
            server.Completed(http =>
            {
                http.RegisterProtobuf<User>();
                http.UseProtobufController();
            });
            server.Run();
        }
    }

客户端

        如果你希望在.Net中使用Websocket客户端,BeetleX同样也提供一个扩展组件BeetleX.Http.Clients,通过这组件可以轻易封装一个Protobuf的Websocket客户端。

public class ProtobufClient : BinaryClient
    {
        public ProtobufClient(string host) : base(host) { }


        public static BinaryDataFactory BinaryDataFactory { get; private set; } = new BinaryDataFactory();


        protected override object OnDeserializeObject(ArraySegment<byte> data)
        {
            return BinaryDataFactory.Deserialize(data);
        }


        protected override ArraySegment<byte> OnSerializeObject(object data)
        {
            return BinaryDataFactory.Serializable(data);
        }
    }

封装完成后就可以使用了

class Program
    {
        static async Task Main(string[] args)
        {
            ProtobufClient.BinaryDataFactory.RegisterComponent<User>();
            var client = new ProtobufClient("ws://localhost");
            User user = new User { Email="henryfan@msn.com", Name="henryfan" };
            var result = await client.ReceiveFrom<User>(user);
            Console.WriteLine(result.Name);
        }
    }

完整示例代码可以访问

https://github.com/beetlex-io/BeetleX-Samples/tree/master/Websocket.ProtobufPacket

https://github.com/beetlex-io/BeetleX-Samples/tree/master/Websocket.MessagePackPacket

BeetleX

开源跨平台通讯框架(支持TLS)
提供高性能服务和大数据处理解决方案

5a330301cd4513ca7701aa8996c0969d.png

https://beetlex-io.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值