.NET Web API WebSocket中间件分享

        WebSocket作为长连接使用较多的通信协议,对于我们的项目中前后端之间的双向通信是比较有用的,多数用于前端需要看到后端对于数据的处理情况,有个进度条返回之类的情况,前后端处于长连接状态时,可以互相的发送消息。对于后端给前端发送的数据处理消息,那么前端可以实时的展示出来,达到一些可见的目的。

  前端在正常的接口传值时,也需要在header中加identity,用来证明身份(最好是随机的,避免多用户使用时消息的混乱)。

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Common.Log;

namespace Common.ServiceCommon
{
    /// 
    /// WebSocket中间件
    /// 
    public class WebSocketMiddleware
    {
        private readonly RequestDelegate m_requestDelegate;
        private readonly ILogHelper m_logHelper;
        private static readonly IDictionary m_processorTypes;

        static WebSocketMiddleware()
        {
            m_processorTypes = new Dictionary();

            foreach (Type type in Assembly.GetEntryAssembly().GetTypes())
            {
                if (type.IsSubclassOf(typeof(MessageProcessor)) && !type.IsAbstract)
                {
                    MessageProcessorRouteAttribute messageProcessorRouteAttribute = type.GetCustomAttribute();

                    if (messageProcessorRouteAttribute != null)
                    {
                        m_processorTypes.Add($"/{messageProcessorRouteAttribute.Template}", type);
                    }
                    else
                    {
                        throw new DealException("消息处理器必须标明MessageProcessorRoute特性。");
                    }
                }
            }
        }

        /// 
        /// WebSocket中间件构造函数
        /// 
        /// 
        /// 
        public WebSocketMiddleware(RequestDelegate requestDelegate, ILogHelper logHelper)
        {
            m_requestDelegate = requestDelegate;
            m_logHelper = logHelper;
        }

        /// 
        /// 连接
        /// 
        /// 
        /// 
        public async Task Invoke(HttpContext context)
        {
            if (!context.WebSockets.IsWebSocketRequest)
            {
                await m_requestDelegate.Invoke(context);
                return;
            }

            string identity = context.Request.Query["identity"].FirstOrDefault(); //需要传identity身份 乱写都行

            if (string.IsNullOrWhiteSpace(identity))
            {
                await m_requestDelegate.Invoke(context);
                return;
            }

            using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource())
            {
                using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync())
                {
                    if (!m_processorTypes.ContainsKey(context.Request.Path))
                    {
                        await m_requestDelegate.Invoke(context);
                        return;
                    }

                    MessageProcessor messageProcessor =
                        (MessageProcessor)context.RequestServices.CreateInstanceFromServiceProvider(m_processorTypes[context.Request.Path], new object[] { identity });

                    Task sendTask = Task.Factory.StartNew(async () =>
                    {
                        // ReSharper disable once AccessToDisposedClosure
                        while (webSocket.State == WebSocketState.Open)
                        {
                            string data = string.Empty;

                            try
                            {
                                // ReSharper disable once AccessToDisposedClosure
                                data = messageProcessor.SendDatas.Take(cancellationTokenSource.Token);
                            }
                            catch (OperationCanceledException)
                            {
                                break;
                            }

                            // ReSharper disable once AccessToDisposedClosure
                            await SendStringAsync(webSocket, data);
                        }
                    });

                    _ = Task.Factory.StartNew(async () =>
                    {
                        while (true)
                        {
                            // ReSharper disable once AccessToDisposedClosure
                            if (webSocket.State != WebSocketState.Open)
                            {
                                // ReSharper disable once AccessToDisposedClosure
                                cancellationTokenSource.Cancel(false);
                                break;
                            }

                            await Task.Delay(10);
                        }
                    });

                    while (webSocket.State == WebSocketState.Open)
                    {
                        (bool success, string data) = await ReceiveStringAsync(webSocket);

                        if (success)
                        {
                            _ = Task.Factory.StartNew(async (state) =>
                            {
                                try
                                {
                                    await messageProcessor.RecieveMessage(data, ((CancellationTokenSource)state).Token);
                                    await m_logHelper.Info(m_processorTypes[context.Request.Path].Name, nameof(MessageProcessor.RecieveMessage), nameof(MessageProcessor.RecieveMessage), data);
                                }
                                catch (Exception exception)
                                {
                                    int httpStatusCode = StatusCodes.Status500InternalServerError;

                                    if (exception is DealException || exception is ResourceException)
                                        httpStatusCode = StatusCodes.Status402PaymentRequired;
                                    
                                    string errorMessage = ExceptionHelper.GetMessage(exception);

                                    await m_logHelper.Error(m_processorTypes[context.Request.Path].Name,
                                                            nameof(MessageProcessor.RecieveMessage),
                                                            httpStatusCode,
                                                            errorMessage,
                                                            nameof(MessageProcessor.RecieveMessage),
                                                            data,
                                                            exception.StackTrace);
                                    
                                    messageProcessor.SendDatas.Add(errorMessage);
                                }
                            }, cancellationTokenSource);
                        }

                        await Task.Delay(10);
                    }

                    await sendTask;
                }
            }
        }

        private static Task SendStringAsync(WebSocket socket, string data) //发送数据
        {
            var segment = new ArraySegment(Encoding.UTF8.GetBytes(data));
            return socket.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None);
        }

        private static async Task> ReceiveStringAsync(WebSocket socket) //接收数据
        {
            ArraySegment buffer = new ArraySegment(new byte[8192]);
            using (MemoryStream memoryStream = new MemoryStream())
            {
                WebSocketReceiveResult result;

                do
                {
                    try
                    {
                        result = await socket.ReceiveAsync(buffer, CancellationToken.None);
                    }
                    catch
                    {
                        return Tuple.Create(false, string.Empty);
                    }

                    memoryStream.Write(buffer.Array, buffer.Offset, result.Count);
                } while (!result.EndOfMessage);

                memoryStream.Seek(0, SeekOrigin.Begin);

                if (result.CloseStatus.HasValue || result.MessageType != WebSocketMessageType.Text)
                {
                    return Tuple.Create(false, string.Empty);
                }

                using (var reader = new StreamReader(memoryStream, Encoding.UTF8))
                {
                    return Tuple.Create(true, await reader.ReadToEndAsync());
                }
            }
        }
    }
}

        当然,只有中间件是不行的,我们还需要对消息进行处理,这里就需要加消息处理器与特性了,用来和正常的controller分开。

using System;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Common.Model;
using Newtonsoft.Json;

namespace Common.ServiceCommon
{
    /// 
    /// 消息处理器路由特性
    /// 
    public class MessageProcessorRouteAttribute : Attribute
    {
        public string Template { get; set; }

        public MessageProcessorRouteAttribute(string template)
        {
            Template = template;
        }
    }

    /// 
    /// 消息处理器
    /// 
    public abstract class MessageProcessor
    {
        internal BlockingCollection SendDatas { get; }
        protected string Identity { get; }

        internal abstract Task RecieveMessage(object parameter, CancellationToken cancellationToken); //接收消息的

        protected MessageProcessor(string identity) //身份标识
        {
            Identity = identity;
            SendDatas = new BlockingCollection();
        }
    }

    /// 
    /// 消息处理器
    /// 
    /// 
    public abstract class MessageProcessor : MessageProcessor
    {
        internal override async Task RecieveMessage(object parameter, CancellationToken cancellationToken) //接收消息的重写
        {
            await RecieveMessage(JsonConvert.DeserializeObject((string)parameter), cancellationToken);
        }

        /// 
        /// 接收消息
        /// 
        /// 
        /// 
        /// 
        public abstract Task RecieveMessage(T parameter, CancellationToken cancellationToken); //给继承的类实现的消息接收方法

        protected void SendMessage(TMessage message) //发送消息
        {
            SendDatas.Add(JsonConvert.SerializeObject(message));
        }

        protected MessageProcessor(string identity) : base(identity) { } //构造函数
    }

    /// 
    /// 数据导出数据结构
    /// 
    public class ExportResult
    {
        public int TotalCount { get; set; } //总行数
        public int ReadCount { get; set; } //读取条数
        public ExportStatusTypeEnum ExportStatus { get; set; } //导出状态
        public string ErrorMessage { get; set; } //错误消息
        public string FilePath { get; set; } //文件路径
    }

    /// 
    /// 数据导入请求
    /// 
    public class ImportRequest
    {
        public string FileName { get; set; } //文件名
    }

    /// 
    /// 分区数据导入请求
    /// 
    public class SystemImportRequest : ImportRequest
    {
        public string SystemID { get; set; } //系统id
    }

    /// 
    /// 导入结果
    /// 
    public class ImportResult //导入结果实体
    {
        public int TotalCount { get; set; } //总条数
        public int WriteCount { get; set; } //导入条数
        public ImportStatusTypeEnum ImportStatus { get; set; } //导入结果
        public string ErrorMessage { get; set; } //错误信息
        public int ErrorCount { get; set; } //错误条数
        public string ErrorFilePath { get; set; } //错误文件路径
    }

    /// 
    /// 查询消息处理器
    /// 
    /// 
    /// 
    public abstract class SearchMessageProcessor : MessageProcessor where TSearchData : ViewModelBase
    {
        protected virtual Expression> GetBaseLinq(TRequest queryCondition) //获取查询条件
        {
            if (queryCondition == null) //请求的参数不为空
                return item => true;

            LinqSearchAttribute linqSearchAttribute = typeof(TSearchData).GetCustomAttribute(); //是否有LinqSearchAttribute特性

            if (linqSearchAttribute != null && !string.IsNullOrWhiteSpace(linqSearchAttribute.GetLinqFunctionName))
            {
                //获取查询条件的方法
                MethodInfo method = typeof(TSearchData).GetMethod(linqSearchAttribute.GetLinqFunctionName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

                if (method != null) //不为null
                {
                    Func>> predicateLinq = method.Invoke(null, null) as Func>>; //实例化并转换

                    if (predicateLinq != null)
                        return predicateLinq(queryCondition); //获取linq查询条件
                }
            }

            return item => true;
        }

        protected SearchMessageProcessor(string identity) : base(identity) { } //构造函数
    }
}

        使用例子如下

[MessageProcessorRoute("abcds")]
    public class ABCD : MessageProcessor
    {
        private readonly ISSOUserService m_ssoUserService;

        public override async Task RecieveMessage(TestData testData, CancellationToken cancellationToken)
        {
            SSOUserInfo ssoUserInfo = m_ssoUserService.GetUser();
            int i = 0;

            while (!cancellationToken.IsCancellationRequested)
            {
                SendMessage($"{Environment.TickCount}_{testData.Parameter}");
                await Task.Delay(1000);
                i++;

                if (i > 5)
                    throw new DealException("测试异常");
            }
        }

        public ABCD(string identity, ISSOUserService ssoUserService) : base(identity)
        {
            m_ssoUserService = ssoUserService;
        }
    }

        当然,我们这里是用了阿里云用来作为文件的上传与下载的载体,所以ImportResult里面是存的文件名,我们和前端会有阿里云文件地址的协议。这里也可以不用阿里云,可以直接用File来作为文件的上传与下载,这是就需要对ImportResult重写一下,将public IFile File { get; set; }加上,自己去通过这个接收就好了。

源码在库gitee链接地址:https://gitee.com/chengdu-dotneter/Framework

  有兴趣的小伙伴可以看看

你可能感兴趣的:(.net各种简单实现记录,websocket,网络协议,.net,中间件,c#)