Asp.NetCoreではミドルウェアを使用してwebsocketを管理しています

18379 ワード

紹介する


ASP.NET Core SignalRは、Webアプリケーションにおけるリアルタイム通信の管理を簡素化するための有用なライブラリです.しかし、WebSocketsを使用するほうがましです.より柔軟で、WebSocketクライアントと互換性があるからです.
Microsoftのドキュメントでは、WebSocketsの良い作業例を見つけました.SignalRは、SignalRが開梱して使用する機能である1つの接続から他の接続にメッセージをブロードキャストできるように接続を管理しています.このロジックが非常に複雑であることを期待して、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();
    }
}

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」を含む.クエリ文字列は、WebSocketをログインユーザに関連付けるユーザ名のパラメータ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();
services.AddSingleton();

CustomWebSocketFactoryは、接続のWebSocketsリストを収集します.
public interface ICustomWebSocketFactory
{
   void Add(CustomWebSocket uws);
   void Remove(string username);
   List All();
   List Others(CustomWebSocket client);
   CustomWebSocket Client(string username);
}

public class CustomWebSocketFactory : ICustomWebSocketFactory
{
   List List;

   public CustomWebSocketFactory()
   {
      List = new List();
   }

   public void Add(CustomWebSocket uws)
   {
      List.Add(uws);
   }

   //when disconnect
   public void Remove(string username) 
   {
      List.Remove(Client(username));
   }

   public List All()
   {
      return List;
   }
   
   public List 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(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);
      }
   }
}

最後に、ConfigureメソッドのStartupクラスに次の内容を追加します.
var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
    ReceiveBufferSize = 4 * 1024
};

app.UseWebSockets(webSocketOptions);
app.UseCustomWebSocketManager();

これにより、Starupクラスは清潔に保たれ、WebSocketsを管理するロジックが拡張され、自分の好みに合わせて柔軟に組織することができます.この文章が好きなら転載しましょう.Asp.NetCoreではミドルウェアを使用してwebsocketを管理しています