Net Core中使用WebSocket默认是没有路由系统的,只能通过Request.Path=="/xxx"
来判断请求,例如:
1 2 3 4 5 6 7 8 91011121314151617181920
| app.Use(async (context, next) =>{ if (context.Request.Path == "/ws") { if (context.WebSockets.IsWebSocketRequest) { WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); await Echo(context, webSocket); } else { context.Response.StatusCode = 400; } } else { await next(); }});
|
要使用类似[HttpGet("/xxx")]
这种特性标签的路由方式,可以自己写一个简单的Attribute
来实现。
这个Attribute
类很简单,只接收一个定义的Path,用来和开头提到的Request.Path
对应。
1 2 3 4 5 6 7 8 910111213
| /// <summary> /// WebSockets执行函数特性标签 /// </summary>[AttributeUsage(AttributeTargets.Method,Inherited = false)] public class WebSocketsAttribute:Attribute { // WebSocket请求的Path public string Path; public WebSocketsAttribute(string path) { Path = path; }}
|
使用一个帮助类来根据请求的PATH
分发给对应的函数。该类中有两个静态方法,实现同样的功能,区别是一个会缓存请求的Path
和处理请求函数,另一个是实时反射解析的,实际使用中当然推荐带缓存的,因为反射对性能的损耗也是很大的。
2.1. 不带缓存的分发函数
1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930313233343536
| /// <summary>/// 分发websockets请求(没有缓存)/// </summary>/// <param name="assemblyName">对应处理程序所在的程序集名称</param>/// <param name="context">HTTP上下文</param>/// <param name="task">TASK任务</param>/// <returns></returns>public static async Task DistributeAsync(string assemblyName, HttpContext context, Func<Task> task){ try { // 获取在WebSockets下面的websocket处理methods var assembly = Assembly.Load(assemblyName); // 根据特性标签获取对应的执行函数 var methods = assembly .GetTypes() .SelectMany(s => s.GetMethods()) .First(f => f .GetCustomAttributes(typeof(WebSocketsAttribute), false) // 这一步获取打了WebSocketsAttribute特性标签的函数 .Any(w => ((WebSocketsAttribute)w).Path == context.Request.Path)); // 过滤找出其中WebSocketsAttribute标签中Path参数对应的执行函数 if (context.WebSockets.IsWebSocketRequest) { var webSockt = await context.WebSockets.AcceptWebSocketAsync(); // 异步调用该方法 await (Task)methods.Invoke(null, new object[] { context, webSockt }); } else { context.Response.StatusCode = 400; } } catch (Exception) { await task(); }}
|
2.2. 带缓存的分发函数
1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031323334353637383940414243444546
| // 使用并发安全的字段类型缓存请求Path与处理请求函数private static ConcurrentDictionary<string, MethodInfo> ExecuteMethods;/// <summary>/// 分发websockets请求(缓存执行函数)/// </summary>/// <param name="assemblyName">对应处理程序所在的程序集名称</param>/// <param name="context">HTTP上下文</param>/// <param name="task">TASK任务</param>/// <returns></returns>public static async Task DistributeAsync(string assemblyName, HttpContext context, Func<Task> task){ if (!ExecuteMethods.Any()) { // 获取在WebSockets下面的websocket处理methods var assembly = Assembly.Load(assemblyName); // 根据特性标签获取对应的执行函数 var methods = assembly .GetTypes() .SelectMany(s => s.GetMethods()) .Select(s => new { ((WebSocketsAttribute) s.GetCustomAttributes(typeof(WebSocketsAttribute), false).First()).Path, s }).ToArray(); ExecuteMethods = new ConcurrentDictionary<string, MethodInfo>(methods.ToDictionary(t => t.Path, t => t.s)); await task(); } else { if (context.WebSockets.IsWebSocketRequest) { var webSockt = await context.WebSockets.AcceptWebSocketAsync(); // 从键值对中获取对应的执行函数 ExecuteMethods.TryGetValue(context.Request.Path, out var method); if (method != null) // 异步调用该方法 await (Task) method.Invoke(null, new object[] {context, webSockt}); } else { context.Response.StatusCode = 400; } }}
|
在startup.cs
中的Config
函数中可以使用我们自己创建的分发函数,需要注意的是我们自定义的函数需要传入程序集的名称,也就是真实的请求处理函数所在的程序集。
由此可见,这种方式使用的一些限制,请求处理函数必须写在一个程序集里面,不过我想一般情况下,没有人会把同一类型的处理函数写在不同的程序集,所以这应该不算是问题。
1 2 3 4 5 6 7 8 91011121314
| /// <summary>/// startup.cs/// </summary>public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory){ ... // 增加websocket app.Use(async (context, next) => { // 将原本的Echo方法换成我们自定义的分发函数 await WebSocketsHandler.DistributeAsync("NetCoreFrame.WebSockets", context, next); }); ...}
|
使用方式很简单,就是一个正常的WebSocket处理函数,只不过标注了我们自己的“路由特性标签”。
1 2 3 4 5 6 7 8 91011121314151617181920
| public class Test{ [WebSockets("/ws")] public static async Task Echo(HttpContext context, WebSocket webSocket) { var buffer = new byte[1024 * 4]; var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); while (!result.CloseStatus.HasValue) { await webSocket.SendAsync( new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None ); result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); } await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); }}
|
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/websockets?view=aspnetcore-2.2
原文地址:
http://haijd.tech/csharp/%E5%9C%A8.net-core%E4%B8%AD%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAwebsocket%E8%B7%AF%E7%94%B1/
.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com