在.Net Core中实现一个WebSocket路由
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?
總結
以上是生活随笔為你收集整理的在.Net Core中实现一个WebSocket路由的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 译 | .NET Core 基础架构进化
- 下一篇: Dapper介绍--Micro-ORM