极简风格网络消息以及分发架构

  之前有一次情况特殊, 只能手撸网络框架代码, 因为时间紧张就需要一个非常极简风格的消息和分发框架了, 只要把网络的发送逻辑跟接收入口跟分发中心绑定, 然后所有操作只需要跟消息中心交互就行了, 就像下面这样 : 

    public class MyIP : INetMessage
    {
        public string ipAddress;
        public int port;
    }
    
    // 接收
    private static void OnReceviedInfo(MyIP info)
    {
        Debug.Log("MyIP " + info.ipAddress);
    }
    MsgCenter.instance.Register<MyIP>(OnReceviedInfo);
    
    // 发送
    MsgCenter.instance.SendNetMessage(new MyIP() { ipAddress = "192.168.1.12" });

  就只需要定义一个类型继承 INetMessage, 然后注册接收回调, 发送的时候直接调用消息中心进行发送即可, 对于临时需求来说非常方便了.

  然后消息中心里面最核心的就是消息的自动注册和分发了, 它需要有一个把对象序列化的工具, 可以是Protobuf, Json序列化等各种工具, 这里因为对数据和效率要求不高, 并且减少工序, 直接用对象转Json然后转字节的方式来了 : 

    // 对象转字节数组  
    public static byte[] StructToBytes<T>(T structObj)
    {
        var jsonStr = LitJson.JsonMapper.ToJson(structObj);
        var sendBytes = System.Text.Encoding.UTF8.GetBytes(jsonStr);
        return sendBytes;
    }

    // 字节转对象
    public static object BytesToStruct(byte[] bytes, int startIndex, int length, Type strcutType)
    {
        var jsonStr = System.Text.Encoding.UTF8.GetString(bytes, startIndex, length);
        var tag = LitJson.JsonMapper.ToObject(jsonStr, strcutType);        // 这个接口插件没有提供, 不过它的函数已经支持这个方法了, 自己添加即可
        return tag;
    }

  对象能够转换字节之后, 就是添加消息收发的逻辑了, 这里使用了类型的名称作为ID, 自动反序列化的关联性就在ID上, 为了极简方案所以使用类名作为ID, 只要类名不重复即可 : 

        // 对象转为发送字节
        public static byte[] StructToSendBuffer<T>(T structObj) where T : INetMessage
        {
            var sendData = StructToBytes<T>(structObj);
            int sendDataLen = sendData.Length;
            var id = typeof(T).Name;
            var sendID = System.Text.Encoding.UTF8.GetBytes(id);
            int sendIDLen = sendID.Length;
            int totalLen = 4 + 4 + sendIDLen + sendDataLen;
            var sendBuffer = new byte[totalLen];

            Array.Copy(BitConverter.GetBytes(totalLen), sendBuffer, 4);
            Array.Copy(BitConverter.GetBytes(sendIDLen), 0, sendBuffer, 4, 4);
            Array.Copy(sendID, 0, sendBuffer, 8, sendIDLen);
            Array.Copy(sendData, 0, sendBuffer, 8 + sendIDLen, sendData.Length);

            return sendBuffer;
        }

  发送结构就如下图 : 

  第一个 byte[4] 代表消息总长度, 这样就可以解决分包粘包的问题了, 不过我们这个临时的都是小消息, 就没有对它进行处理.

  第二个 byte[4] 代表后面ID的长度, 因为ID使用的是类名称, 长度不一定, 所以要添加这个.

  第三个 ID 就是类名.

  第四个 就是对象的序列化了, 它的长度可以用 (总长-8-ID长度) 得到, 也很简单.

 

  所以发送数据就这样简单完成了, 消息中心的发送可以直接一个代理绑定即可 : 

public System.Action<byte[]> _sendMessageByte = null;
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
public void SendNetMessage<T>(T message) where T : INetMessage
{
    if(_sendMessageByte != null)
    {
        var sendBuffer = StructToSendBuffer<T>(message);
        _sendMessageByte.Invoke(sendBuffer);
    }
}

 

  接下来就是非常有意思的接收到数据之后的反序列化了, 因为所有消息都是继承 INetMessage 的, 所以可以通过 Assembly 获取全部消息对象, 然后创建反序列化回调就行了, 看下面的代码 : 

Dictionary<string, System.Func<byte[], int, int, INetMessage>> _messageDeserializer = new ......

private void InitDeserializeFunc()
{
    var baseMsgType = typeof(INetMessage);
    foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies())
    {
        var types = assembly.GetTypes();
        foreach(var type in types)
        {
            var interfaces = type.GetInterface(baseMsgType.Name);
            if(interfaces != null)
            {
                _messageDeserializer[type.Name] = (_bytes, _startIndex, _length) =>
                {
                    var data = BytesToStruct(_bytes, _startIndex, _length, type);
                    if(data != null)
                    {
                        return (INetMessage)data;
                    }
                    return null;
                };
            }
        }
    }    
}
    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
    public void ReceiveNetMessage(byte[] message)
    {
        try
        {
            int len = System.BitConverter.ToInt32(message, 0);
            int idLen = System.BitConverter.ToInt32(message, 4);
            string id = System.Text.Encoding.UTF8.GetString(message, 8, idLen);
            var func = _messageDeserializer.TryGetNullableValue(id);
            if(func != null)
            {
                var msg = func.Invoke(message, 8 + idLen, len - 8 - idLen);
                if(msg != null)
                {
                    lock(_receiveLock)
                    {
                        _receivedMessage.Add(msg);
                    }
                }
            }
        }
        catch { }
    }

  这里 ReceiveNetMessage 是对接到网络接收端的, 省掉了对分包/粘包这些的逻辑, 这里从消息中读取出了 ID, 然后从容器里面找到反序列化方法, 然后反序列化到已接收队列中. 

  消息分发的逻辑就使用了比较低效的 DynamicInvoke 的方式, 通过注册回调来自动分发 : 

Dictionary<Type, Delegate> _msgDispatcher = new Dictionary<Type, Delegate>();

public void Register<T>(System.Action<T> action) where T : INetMessage
{
    var type = typeof(T);
    var call = _msgDispatcher.TryGetNullableValue(type);
    if(call == null)
    {
        _msgDispatcher[type] = action;
    }
    else
    {
        _msgDispatcher[type] = (Delegate.Combine(call, action));
    }
}
private void Update()
{
    if(_receivedMessage.Count > 0)
    {
        lock(_receiveLock)
        {
            for(int i = 0; i < _receivedMessage.Count; i++)
            {
                var msg = _receivedMessage[i];
                var type = msg.GetType();
                var call = _msgDispatcher.TryGetNullableValue(type);
                if(call != null)
                {
                    call.DynamicInvoke(msg);
                }
            }
            _receivedMessage.Clear();
        }
    }
}

  这个 Update 如果是 Unity 的生命周期的话, 那么接收消息回调就是在主线程里面的了, 如果是开了一个线程来 Tick 这个 Update 的话, 它就是在非主线程的了, 这个需要注意, 为了方便起见一般消息中心就继承 MonoBehaviour 了.  

  以上, 极简风格, 极垃圾的性能(可以优化抢救的)...

 

--------------- 完整代码 -----------------

using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using System.Reflection;

namespace NetWorkingModule
{
    public class MsgCenter : Singleton<MsgCenter>
    {
        private static readonly Dictionary<string, System.Func<byte[], int, int, INetMessage>> _messageDeserializer
            = new Dictionary<string, System.Func<byte[], int, int, INetMessage>>();
        private readonly Dictionary<Type, Delegate> _msgDispatcher = new Dictionary<Type, Delegate>();

        private static volatile List<INetMessage> _receivedMessage = new List<INetMessage>();
        private static object _receiveLock = new object();

        public System.Action<byte[]> _sendMessageByte = null;
        public event System.Action<byte[]> sendMessageByte
        {
            add { _sendMessageByte += value; }
            remove { _sendMessageByte -= value; }
        }

        private System.Threading.Thread _update = null;

        #region Override Funcs
        protected override void Initialize()
        {
            InitDeserializeFunc();
        }

        protected override void UnInitialize()
        {
        }
        #endregion

        #region Main Funcs
        private void InitDeserializeFunc()
        {
            var baseMsgType = typeof(INetMessage);
            foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                var types = assembly.GetTypes();
                foreach(var type in types)
                {
                    var interfaces = type.GetInterface(baseMsgType.Name);
                    if(interfaces != null)
                    {
                        _messageDeserializer[type.Name] = (_bytes, _startIndex, _length) =>
                        {
                            var data = BytesToStruct(_bytes, _startIndex, _length, type);
                            if(data != null)
                            {
                                return (INetMessage)data;
                            }
                            return null;
                        };
                    }
                }
            }
            if(_update != null)
            {
                _update.Abort();
            }
            _update = new System.Threading.Thread(() =>
            {
                while(true)
                {
                    Update();
                    System.Threading.Thread.Sleep(10);
                }
            });
            _update.Start();
        }

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
        public void SendNetMessage<T>(T message) where T : INetMessage
        {
            if(_sendMessageByte != null)
            {
                var sendBuffer = StructToSendBuffer<T>(message);
                _sendMessageByte.Invoke(sendBuffer);
            }
        }

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
        public void ReceiveNetMessage(byte[] message)
        {
            try
            {
                int len = System.BitConverter.ToInt32(message, 0);
                int idLen = System.BitConverter.ToInt32(message, 4);
                string id = System.Text.Encoding.UTF8.GetString(message, 8, idLen);
                var func = _messageDeserializer.TryGetNullableValue(id);
                if(func != null)
                {
                    var msg = func.Invoke(message, 8 + idLen, len - 8 - idLen);
                    if(msg != null)
                    {
                        lock(_receiveLock)
                        {
                            _receivedMessage.Add(msg);
                        }
                    }
                }
            }
            catch { }
        }

        public void Register<T>(System.Action<T> action) where T : INetMessage
        {
            var type = typeof(T);
            var call = _msgDispatcher.TryGetNullableValue(type);
            if(call == null)
            {
                _msgDispatcher[type] = action;
            }
            else
            {
                _msgDispatcher[type] = (Delegate.Combine(call, action));
            }
        }
        public void UnRegister<T>(System.Action<T> action) where T : INetMessage
        {
            var type = typeof(T);
            var call = _msgDispatcher.TryGetNullableValue(type);
            if(call != null)
            {
                _msgDispatcher[type] = Delegate.Remove(call, action);
            }
        }
        #endregion

        #region Help Funcs
        // 对象转为发送字节
        public static byte[] StructToSendBuffer<T>(T structObj) where T : INetMessage
        {
            var sendData = StructToBytes<T>(structObj);
            int sendDataLen = sendData.Length;
            var id = typeof(T).Name;
            var sendID = System.Text.Encoding.UTF8.GetBytes(id);
            int sendIDLen = sendID.Length;
            int totalLen = 4 + 4 + sendIDLen + sendDataLen;
            var sendBuffer = new byte[totalLen];

            Array.Copy(BitConverter.GetBytes(totalLen), sendBuffer, 4);
            Array.Copy(BitConverter.GetBytes(sendIDLen), 0, sendBuffer, 4, 4);
            Array.Copy(sendID, 0, sendBuffer, 8, sendIDLen);
            Array.Copy(sendData, 0, sendBuffer, 8 + sendIDLen, sendData.Length);

            return sendBuffer;
        }

        // 对象转字节数组  
        public static byte[] StructToBytes<T>(T structObj)
        {
            var jsonStr = LitJson.JsonMapper.ToJson(structObj);
            var sendBytes = System.Text.Encoding.UTF8.GetBytes(jsonStr);
            return sendBytes;
        }

        // 字节转对象
        public static object BytesToStruct(byte[] bytes, int startIndex, int length, Type strcutType)
        {
            var jsonStr = System.Text.Encoding.UTF8.GetString(bytes, startIndex, length);
            var tag = LitJson.JsonMapper.ToObject(jsonStr, strcutType);
            return tag;
        }
        #endregion

        private void Update()
        {
            if(_receivedMessage.Count > 0)
            {
                lock(_receiveLock)
                {
                    for(int i = 0; i < _receivedMessage.Count; i++)
                    {
                        var msg = _receivedMessage[i];
                        var type = msg.GetType();
                        var call = _msgDispatcher.TryGetNullableValue(type);
                        if(call != null)
                        {
                            Core.ThreadMaster.RunOnMainThread(()=> { call.DynamicInvoke(msg); });
                        }
                    }
                    _receivedMessage.Clear();
                }
            }
        }
    }
}

PS : 为了在编辑器等情况下使用, 继承改为普通的 SingleTon 了, Update 的更新也进了工作线程里, 回调通过 SynchronizationContext 来回到主线程, 性能更惨了哈哈 

  

也贴一下标记主线程的方法吧 : 

    public static int MainThreadId
    {
        get; private set;
    }
    public static SynchronizationContext UnitySynchronizationContext
    {
        get; private set;
    }

#if UNITY_EDITOR
    [UnityEditor.InitializeOnLoadMethod()]
#endif
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static void Init()
    {
        UnitySynchronizationContext = SynchronizationContext.Current;
        MainThreadId = Thread.CurrentThread.ManagedThreadId;
        Debug.Log("Main Thread ID : " + MainThreadId);
    }
    
    public static void RunOnMainThread(System.Action call)
    {
        if(Thread.CurrentThread.ManagedThreadId == MainThreadId)
        {
            call.Invoke();
        }
        else
        {
            UnitySynchronizationContext.Post((_state) => { call.Invoke(); }, null);
        }
    }

 

posted @ 2021-09-01 10:44  tiancaiKG  阅读(216)  评论(0编辑  收藏  举报