在.Net Core中实现一个WebSocket路由
.Net Core中使用WebSocket默认是没有路由系统的,只能通过Request.Path=="/xxx"
来判断请求,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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
来实现。
1. 实现Attribute
特性类
这个Attribute
类很简单,只接收一个定义的Path,用来和开头提到的Request.Path
对应。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/// <summary>
/// WebSockets执行函数特性标签
/// </summary>
[AttributeUsage(AttributeTargets.Method,Inherited = false)]
public class WebSocketsAttribute:Attribute
{
// WebSocket请求的Path
public string Path;
public WebSocketsAttribute(string path)
{
Path = path;
}
}
|
2. 实现一个分发WebSocket请求的帮助类
使用一个帮助类来根据请求的PATH
分发给对应的函数。该类中有两个静态方法,实现同样的功能,区别是一个会缓存请求的Path
和处理请求函数,另一个是实时反射解析的,实际使用中当然推荐带缓存的,因为反射对性能的损耗也是很大的。
2.1. 不带缓存的分发函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
/// <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
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
// 使用并发安全的字段类型缓存请求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;
}
}
}
|
3. 在.Net Core中调用分发函数
在startup.cs
中的Config
函数中可以使用我们自己创建的分发函数,需要注意的是我们自定义的函数需要传入程序集的名称,也就是真实的请求处理函数所在的程序集。
由此可见,这种方式使用的一些限制,请求处理函数必须写在一个程序集里面,不过我想一般情况下,没有人会把同一类型的处理函数写在不同的程序集,所以这应该不算是问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/// <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);
});
...
}
|
4. 使用方式
使用方式很简单,就是一个正常的WebSocket处理函数,只不过标注了我们自己的“路由特性标签”。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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);
}
}
|
5. 参考资料
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/websockets?view=aspnetcore-2.2