65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconemptyStarIconemptyStarIconemptyStarIcon

2.00/5 (4投票s)

2018年9月3日

CPOL

2分钟阅读

viewsIcon

15936

一种组织 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 的逻辑可以增长,从而为您提供根据自己的喜好组织它的灵活性。

关注点

 

历史

在此处保持您所做的任何更改或改进的实时更新。

© . All rights reserved.