一个用于在 ASP.NET Core 中管理 Web Socket 的中间件





2.00/5 (4投票s)
一种组织 WebSocket 管理逻辑的方法,保持 Startup 类简洁。
引言
ASP.NET Core SignalR 是一个有用的库,可以简化 Web 应用程序中实时功能的管理。 在这种情况下,我更喜欢使用 WebSockets,因为我想要更多的灵活性和与任何 WebSocket 客户端的兼容性。
在 Microsoft 的文档中,我找到了一个很好的 WebSockets 工作示例。 仍然需要管理连接,以便能够从一个连接向其他连接广播消息,这是 SignalR 开箱即用的功能。 考虑到这种逻辑可能会非常复杂,我想将其从 Startup 类本身中移除。
背景
要了解 ASP.NET Core 中的 WebSockets 支持,您可以查看此处。
如果您想了解中间件以及如何在 ASP.NET Core 中编写它,请阅读此链接。
使用代码
首先,您需要将 Microsoft.AspNetCore.WebSockets
包添加到您的项目中。
现在,您可以创建一个扩展方法和类来管理 WebSockets
public static class WebSocketExtensions
{
public static IApplicationBuilder UseCustomWebSocketManager(this IApplicationBuilder app)
{
return app.UseMiddleware<CustomWebSocketManager>();
}
}
public class CustomWebSocketManager
{
private readonly RequestDelegate _next;
public CustomWebSocketManager(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, ICustomWebSocketFactory wsFactory, ICustomWebSocketMessageHandler wsmHandler)
{
if (context.Request.Path == "/ws")
{
if (context.WebSockets.IsWebSocketRequest)
{
string username = context.Request.Query["u"];
if (!string.IsNullOrEmpty(username))
{
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
CustomWebSocket userWebSocket = new CustomWebSocket()
{
WebSocket = webSocket,
Username = username
};
wsFactory.Add(userWebSocket);
await wsmHandler.SendInitialMessages(userWebSocket);
await Listen(context, userWebSocket, wsFactory, wsmHandler);
}
}
else
{
context.Response.StatusCode = 400;
}
}
await _next(context);
}
private async Task Listen(HttpContext context, CustomWebSocket userWebSocket, ICustomWebSocketFactory wsFactory, ICustomWebSocketMessageHandler wsmHandler)
{
WebSocket webSocket = userWebSocket.WebSocket;
var buffer = new byte[1024 * 4];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
await wsmHandler.HandleMessage(result, buffer, userWebSocket, wsFactory);
buffer = new byte[1024 * 4];
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
wsFactory.Remove(userWebSocket.Username);
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
}
在这种情况下,WebSockets 请求的 URL 中始终包含 "/ws"。 查询字符串包含一个参数 u
,用于与登录用户关联的用户名。
CustomWebSocket
是一个包含 WebSocket 和用户名的类
public class CustomWebSocket
{
public WebSocket WebSocket { get; set; }
public string Username { get; set; }
}
我还创建了一个自定义 WebSocket 消息
class CustomWebSocketMessage
{
public string Text { get; set; }
public DateTime MessagDateTime { get; set; }
public string Username { get; set; }
public WSMessageType Type { get; set; }
}
其中 Type 是您可能拥有的不同类型消息的枚举。
在 Startup
类中,您需要注册以下服务
services.AddSingleton<ICustomWebSocketFactory, CustomWebSocketFactory>();
services.AddSingleton<ICustomWebSocketMessageHandler, CustomWebSocketMessageHandler>();
其中 CustomWebSocketFactory
负责收集所有已连接的 WebSockets:
public interface ICustomWebSocketFactory
{
void Add(CustomWebSocket uws);
void Remove(string username);
List<CustomWebSocket> All();
List<CustomWebSocket> Others(CustomWebSocket client);
CustomWebSocket Client(string username);
}
public class CustomWebSocketFactory : ICustomWebSocketFactory
{
List<CustomWebSocket> List;
public CustomWebSocketFactory()
{
List = new List<CustomWebSocket>();
}
public void Add(CustomWebSocket uws)
{
List.Add(uws);
}
//when disconnect
public void Remove(string username)
{
List.Remove(Client(username));
}
public List<CustomWebSocket> All()
{
return List;
}
public List<CustomWebSocket> Others(CustomWebSocket client)
{
return List.Where(c => c.Username != client.Username).ToList();
}
public CustomWebSocket Client(string username)
{
return List.First(c=>c.Username == username);
}
}
而 CustomWebSocketMessageHandler
包含有关消息的逻辑(例如,如果需要在连接时发送任何消息以及如何响应传入的消息)
public interface ICustomWebSocketMessageHandler
{
Task SendInitialMessages(CustomWebSocket userWebSocket);
Task HandleMessage(WebSocketReceiveResult result, byte[] buffer, CustomWebSocket userWebSocket, ICustomWebSocketFactory wsFactory);
Task BroadcastOthers(byte[] buffer, CustomWebSocket userWebSocket, ICustomWebSocketFactory wsFactory);
Task BroadcastAll(byte[] buffer, CustomWebSocket userWebSocket, ICustomWebSocketFactory wsFactory);
}
public class CustomWebSocketMessageHandler : ICustomWebSocketMessageHandler
{
public async Task SendInitialMessages(CustomWebSocket userWebSocket)
{
WebSocket webSocket = userWebSocket.WebSocket;
var msg = new CustomWebSocketMessage
{
MessagDateTime = DateTime.Now,
Type = WSMessageType.anyType,
Text = anyText,
Username = "system"
};
string serialisedMessage = JsonConvert.SerializeObject(msg);
byte[] bytes = Encoding.ASCII.GetBytes(serialisedMessage);
await webSocket.SendAsync(new ArraySegment<byte>(bytes, 0, bytes.Length), WebSocketMessageType.Text, true, CancellationToken.None);
}
public async Task HandleMessage(WebSocketReceiveResult result, byte[] buffer, CustomWebSocket userWebSocket, ICustomWebSocketFactory wsFactory)
{
string msg = Encoding.ASCII.GetString(buffer);
try
{
var message = JsonConvert.DeserializeObject<CustomWebSocketMessage>(msg);
if (message.Type == WSMessageType.anyType)
{
await BroadcastOthers(buffer, userWebSocket, wsFactory);
}
}
catch (Exception e)
{
await userWebSocket.WebSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
}
}
public async Task BroadcastOthers(byte[] buffer, CustomWebSocket userWebSocket, ICustomWebSocketFactory wsFactory)
{
var others = wsFactory.Others(userWebSocket);
foreach (var uws in others)
{
await uws.WebSocket.SendAsync(new ArraySegment<byte>(buffer, 0, buffer.Length), WebSocketMessageType.Text, true, CancellationToken.None);
}
}
public async Task BroadcastAll(byte[] buffer, CustomWebSocket userWebSocket, ICustomWebSocketFactory wsFactory)
{
var all = wsFactory.All();
foreach (var uws in all)
{
await uws.WebSocket.SendAsync(new ArraySegment<byte>(buffer, 0, buffer.Length), WebSocketMessageType.Text, true, CancellationToken.None);
}
}
}
最后,在 Startup
类中的 Configure
方法中添加以下内容
var webSocketOptions = new WebSocketOptions()
{
KeepAliveInterval = TimeSpan.FromSeconds(120),
ReceiveBufferSize = 4 * 1024
};
app.UseWebSockets(webSocketOptions);
app.UseCustomWebSocketManager();
因此,通过这种方式,Startup
类保持简洁,并且管理 WebSockets 的逻辑可以增长,从而为您提供根据自己的喜好组织它的灵活性。
关注点
历史
在此处保持您所做的任何更改或改进的实时更新。