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
有兴趣的小伙伴可以看看