ASP.NET Core WebSockets

12,403

Solution 1

I've implemented a WebSocket server in ASP.NET core using these docs and it worked out quite well for me: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/websockets?view=aspnetcore-3.1

The main idea is that after you accept a request with AcceptWebSocketAsync() you take the returned WebSocket object and use it to send and receive. Typically you would create a loop, calling ReceiveAsync until some condition is met (you determine the session is done when you receive a certain message, or the client disconnects, etc). As the docs state, When using a WebSocket, you must keep the middleware pipeline running for the duration of the connection. So if you're passing that WebSocket connection off to a background worker to perform send/receive on, you need to keep that pipeline open for the duration of your interactions with that client. Then when you're done you signal to the middleware that the connection is finished and it can unwind.

I suspect not keeping the connection open and looping is your issue. I haven't used MapConnectionHandler for WebSockets before, so this might not work, but it's possible the above strategy will be helpful to you, or follow the docs with a background worker like so:

app.Use(async (context, next) => {
    var socket = await context.WebSockets.AcceptWebSocketAsync();
    var socketFinishedTcs = new TaskCompletionSource<object>();

    BackgroundSocketProcessor.AddSocket(socket, socketFinishedTcs); 

    await socketFinishedTcs.Task;
});

Solution 2

So, I accomplished my goal. Complete use-case is here: https://github.com/Yucked/Rhapsody/blob/beta/src/Controllers/WebSocketHandler.cs

This approach uses System.IO.Pipelines. I don't have the old source code as I scrapped it and can't figure out why connections were being dropped before even after keeping the pipeline open but hey it works now!

            app.UseEndpoints(endpoints => {
                endpoints.MapControllers();
                endpoints.MapConnectionHandler<WebSocketHandler>("/player/{playerId}");
            });
        public override async Task OnConnectedAsync(ConnectionContext connection) {
            var httpContext = connection.GetHttpContext();

            if (!httpContext.WebSockets.IsWebSocketRequest) {
                await httpContext.Response.CompleteAsync();
                return;
            }

            await httpContext.WebSockets.AcceptWebSocketAsync()
               .ContinueWith(async task => {
                    var webSocket = await task;
                    await HandleConnectionAsync(webSocket);
                });
        }

        private async Task HandleConnectionAsync(WebSocket websocket) {
            try {
                do {
                    var memory = writer.GetMemory(BUFFER_SIZE);
                    var receiveResult = await webSocket.ReceiveAsync(memory, CancellationToken.None);
                    if (!receiveResult.EndOfMessage) {
                        writer.Advance(receiveResult.Count);
                        continue;
                    }

                    await writer.FlushAsync();
                } while (webSocket.State == WebSocketState.Open);
            }
            catch (Exception exception) {
                await writer.CompleteAsync(exception);  
            }
        }
Share:
12,403

Related videos on Youtube

Yucked
Author by

Yucked

ALL FATHER GIMME SKRRRRRRRRTTT

Updated on June 04, 2022

Comments

  • Yucked
    Yucked almost 2 years

    I'm trying to have a WebSocket server up and running on ASP.NET Core. I created an empty web project dotnet new web changed the Program.cs to:

    public static void Main(string[] args) {
        Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder => {
            webBuilder.UseStartup<Startup>();
        })
        .Build()
        .Run();
    }
    

    And Startup.cs's ConfigureServices method to:

    public void ConfigureServices(IServiceCollection services) {
        services.AddControllers();
        services.AddWebSockets();
    }
    

    And Configure method to:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
        app.UseWebSockets();
        app.UseRouting();
        app.UseEndpoints(endpoints => {
            endpoints.MapControllers();
            endpoints.MapConnectionHandler<WebSocketHandler>("/ws");
        });
    }
    

    And my WebSocketHandler's OnConnectedAsync method I've the following:

    public override async Task OnConnectedAsync(ConnectionContext connection) 
    {
        var context = connection.GetHttpContext();
        var endpoint = $"{connection.RemoteEndPoint}";
    
        if (!context.WebSockets.IsWebSocketRequest) {
            connection.Abort();
            _logger.LogCritical($"Request from {endpoint} endpoint aborted.");
            return;
        }
    
        var websocket = await context.WebSockets.AcceptWebSocketAsync();
        _logger.LogInformation($"WebSocket request from {endpoint} endpoint accepted!");
    }
    

    The problem arises when I try to connect to APP_URL/ws and each time the server closes the connection as soon as it receives the request. Here are the logs: https://pastebin.com/raw/34yu7thw

    If I place a Task.Delay(-1) at the end of OnConnectedAsync method, it keeps the connection open but drops incoming connections.

    I have searched MSDocs and haven't been able to find much documentation on how to use MapConnectionHandler<T>.

    Would it be safe for me to have a while loop which receives messages from multiple clients in OnConnectedAsync? Is this not the right way to handle websocket connections? Is MapConnectionHandler<T> transient?

    I'm really confused and can't figure out it's behavior.

  • Yucked
    Yucked over 4 years
    Hey, I tried looping and so forth and it seems MapConnectionHandler isn't suitable for it. Building a proper middleware fixed the problem for me.
  • Macindows
    Macindows over 3 years
    I get BackgroundSocketProcessor not defined? any library i am missing?
  • Swimburger
    Swimburger over 2 years
    How does one actually pass the socket to the background worker, what's in BackgroundSocketProcessor.AddSocket?