MMO基础游戏服务器架构(三):多线程消息队列

更多代码细节,球球各位观众老爷给鄙人的开源项目点个Star,持续更新中~ [项目开源地址]

3.单例消息分发类:MessageRouter

消息包 MessageBlock:

使用值类型Struct降低GC

public struct MessageBlock
{
    public BaseConnection sender;
    public IMessage message;
}

BaseManager< T >:泛型类型安全的单例对象类,继承使用

namespace Common.Summer
{
	/// 
	/// 线程安全
	/// 
	/// 泛型类型
	public class BaseManager<T> where T : new()
	{
		private static T? _instance;
		private static object _lock = new();
		public static T Instance
		{
			get
			{
				if (_instance != null) return _instance;
				lock (_lock)
				{
					_instance ??= new T();
				}
				return _instance;
			}
		}
	}
}

MessageRouter采用生产消费者模型处理异步消息队列

Channel是核心数据结构,线程安全的数据分发,ConcurrentDictionary,线程安全的字典用于处理消息回调,Channel作为消息队列的数据结构载体,CancellationTokenSource用于处理多线程Task的异常终止,workCount是此时工作的线程数量,使用原子操作来保证线程安全

using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Channels;
using Google.Protobuf;
using Serilog;

namespace Common.Summer.Network
{
    public struct MessageBlock
    {
        public BaseConnection Sender;
        public IMessage Message;
    }

    public class MessageRouter : BaseManager<MessageRouter>
    {
        private static readonly MethodInfo FireMethod = typeof(MessageRouter).GetMethod(
            nameof(Fire), BindingFlags.NonPublic | BindingFlags.Instance); //反射获取Fire方法

        private static readonly ConcurrentDictionary<Type, Action<MessageRouter, BaseConnection, IMessage>> 
            FireDelegates = new();

        private static readonly ConcurrentDictionary<Type, PropertyInfo[]> MessagePropertiesCache = new();

        private readonly Channel<MessageBlock> _messageChannel = Channel.CreateUnbounded<MessageBlock>();
        private readonly ConcurrentDictionary<string, Delegate> _messageHandlers = new();

        private CancellationTokenSource _cts;
        private int _activeWorkers;

        public bool Running { get; private set; }

        public delegate void MessageHandler<in T>(BaseConnection sender, T msg) where T : IMessage;

        public void Subscribe<T>(MessageHandler<T> handler) where T : IMessage
        {
            var typeKey = typeof(T).FullName;
            _messageHandlers.AddOrUpdate(
                typeKey, _ => handler,
                (_, existing) => Delegate.Combine(existing, handler));

            Log.Verbose("Subscribed to {MessageType} - Current handlers: {HandlerCount}", 
                typeof(T).Name, 
                ((MessageHandler<T>)_messageHandlers[typeKey])?.GetInvocationList().Length ?? 0);
        }

        public void Unsubscribe<T>(MessageHandler<T> handler) where T : IMessage
        {
            var typeKey = typeof(T).FullName;
            _messageHandlers.AddOrUpdate(
                typeKey, _ => null,
                (_, existing) => Delegate.Remove(existing, handler));

            if (_messageHandlers.TryGetValue(typeKey, out var current) && current == null)
            {
                _messageHandlers.TryRemove(typeKey, out _);
            }
        }

        private void Fire<T>(BaseConnection sender, T message) where T : IMessage
        {
            if (!_messageHandlers.TryGetValue(typeof(T).FullName, out var handler)) return;
            try
            {
                ((MessageHandler<T>)handler)?.Invoke(sender, message);
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Error processing {MessageType} message", typeof(T).Name);
            }
        }

        public void PostMessage(BaseConnection sender, IMessage message)
        {
            try
            {
                if (!_messageChannel.Writer.TryWrite(new MessageBlock { Sender = sender, Message = message }))
                {
                    Log.Warning("Message queue full, message dropped");
                }
            }
            catch (ChannelClosedException ex)
            {
                Log.Error(ex, "Failed to post message to closed channel");
            }
        }

        public void Start(int concurrencyLevel = 8)
        {
            if (Running) return;
            
            Running = true;
            concurrencyLevel = Math.Clamp(concurrencyLevel, 1, Environment.ProcessorCount * 2);
            _cts = new CancellationTokenSource();

            for (var i = 0; i < concurrencyLevel; i++)
            {
                Task.Factory.StartNew(
                    () => ProcessMessagesAsync(_cts.Token),
                    _cts.Token,
                    TaskCreationOptions.LongRunning | TaskCreationOptions.DenyChildAttach,
                    TaskScheduler.Default);
            }
        }

        public async Task StopAsync()
        {
            if (!Running) return;
            
            Running = false;
            _cts.Cancel();
            _messageChannel.Writer.Complete();

            while (_activeWorkers > 0)
            {
                await Task.Delay(50);
            }
            
            _cts.Dispose();
            _cts = null;
        }

        private async Task ProcessMessagesAsync(CancellationToken ct)
        {
            Interlocked.Increment(ref _activeWorkers);
            try
            {
                while (await _messageChannel.Reader.WaitToReadAsync(ct).ConfigureAwait(false))
                    while (_messageChannel.Reader.TryRead(out var message))
                        ProcessMessage(message.Sender, message.Message);
            }
            catch (OperationCanceledException)
            {
                
            }
            finally
            {
                Interlocked.Decrement(ref _activeWorkers);
                Log.Debug("Worker thread exiting");
            }
        }
        
        private readonly ConcurrentQueue<(IMessage Message, BaseConnection Sender)> queue = new(); //线程安全

        /// 
        /// bfs遍历反射(也可以通过dfs减少代码量)
        /// 
        private void ProcessMessage(BaseConnection sender, IMessage message)
        {
            queue.Clear();  //清空上次残留的消息
            queue.Enqueue((message, sender));

            while (!queue.IsEmpty)
            {
                if (!queue.TryDequeue(out var result)) continue;
                var (currentMessage, currentSender) = result;
                TriggerMessageHandlers(currentSender, currentMessage);

                var messageType = currentMessage.GetType();
                var properties = MessagePropertiesCache.GetOrAdd(messageType, t =>
                    t.GetProperties()
                        .Where(p => !p.Name.Equals("Parser") && 
                                    !p.Name.Equals("Descriptor") &&
                                    typeof(IMessage).IsAssignableFrom(p.PropertyType))
                        .ToArray());

                foreach (var property in properties)
                {
                    if (property.GetValue(currentMessage) is IMessage childMessage)
                    {
                        queue.Enqueue((childMessage, currentSender));
                    }
                }
            }
        }

        private void TriggerMessageHandlers(BaseConnection sender, IMessage message)
        {
            var messageType = message.GetType();
            var handler = FireDelegates.GetOrAdd(messageType, type =>
            {
                var method = FireMethod.MakeGenericMethod(type);
                return CreateHandlerDelegate(method);
            });

            handler(this, sender, message);
        }

        private static Action<MessageRouter, BaseConnection, IMessage> CreateHandlerDelegate(MethodInfo method)
        {
            var routerParam = Expression.Parameter(typeof(MessageRouter));
            var senderParam = Expression.Parameter(typeof(BaseConnection));
            var messageParam = Expression.Parameter(typeof(IMessage));
            var convertedMessage = Expression.Convert(messageParam, method.GetParameters()[1].ParameterType);
            
            var callExpr = Expression.Call(
                routerParam,
                method,
                senderParam,
                convertedMessage);

            return Expression.Lambda<Action<MessageRouter, BaseConnection, IMessage>>(
                callExpr,
                routerParam,
                senderParam,
                messageParam).Compile();
        }
    }
}

你可能感兴趣的:(MMO双端游戏架构,游戏,服务器,架构,c#)