Search code examples
c#websocket

C# WebSocket Server not responding to pings


I've managed to write a csharp websocket server and clients can connect, however the clients send a ping message but the server doesnt respond with a pong so the client drops the connection.

On further investigation, it turns out I need to have a ReadAsync running to capture the ping message, but then it's in an await state so blocks everything!

I would love to get to the underlying socket so I could do a socket.available to check for read data but thats not possible.

So how are you supposed to write a websocket server in c# (and dont say SignalR!) that responds to a ping and can determine if the client has dropped!

using System.Net.Sockets;
using System.Net.WebSockets;
using System.Text;

class Program
{
    static async Task ClientHandler(HttpContext context)
    {
        using WebSocket socket = await context.WebSockets.AcceptWebSocketAsync();
        Console.WriteLine("Websocket client connected from {0}", socket.SubProtocol);

        var buffer = new byte[1024 * 4];
        WebSocketReceiveResult payload = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

        string? message = Encoding.UTF8.GetString(buffer);
        Console.WriteLine("Received {0}", message);

        // workaround for replying to ping from client which requires ReceiveAsync to be active
        // Task.Run(async () =>
        // {
        //     await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
        // });

        while (socket.State == WebSocketState.Open)
        {
            Console.WriteLine("Sending message...");
            byte[] msg = System.Text.Encoding.Default.GetBytes("Hello, World!");
            await socket.SendAsync(new ArraySegment<byte>(msg, 0, msg.Length), payload.MessageType, true, CancellationToken.None);

            Thread.Sleep(200);
        }

        await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "No more messages", CancellationToken.None);
        Console.WriteLine("Connection closed");
    }

    static void Main()
    {
        var app = WebApplication.CreateBuilder().Build();

        app.UseWebSockets();

        app.Use(async (context, next) =>
        {
            if (context.Request.Path == "/")
                if (context.WebSockets.IsWebSocketRequest)
                    await ClientHandler(context);
                else
                    context.Response.StatusCode = StatusCodes.Status400BadRequest;
            else
                await next(context);
        });

        app.Run("http://127.0.0.1:8001");
    }
}

Solution

  • check this code:

    class Program
    {
        static async Task ClientHandler(HttpContext context)
        {
            using WebSocket socket = await context.WebSockets.AcceptWebSocketAsync();
            Console.WriteLine("WebSocket client connected.");
    
            var buffer = new byte[1024 * 4];
    
            // Task to continuously receive messages
            var receiveTask = Task.Run(async () =>
            {
                while (socket.State == WebSocketState.Open)
                {
                    var result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                    
                    if (result.MessageType == WebSocketMessageType.Close)
                    {
                        await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
                        Console.WriteLine("WebSocket client disconnected.");
                    }
                    else if (result.MessageType == WebSocketMessageType.Text)
                    {
                        var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
                        Console.WriteLine("Received message: {0}", message);
                    }
                    else if (result.MessageType == WebSocketMessageType.Binary)
                    {
                        Console.WriteLine("Received binary message of length: {0}", result.Count);
                    }
                     
                }
            });
    
            // Task to continuously send messages
            var sendTask = Task.Run(async () =>
            {
                while (socket.State == WebSocketState.Open)
                {
                    var msg = Encoding.UTF8.GetBytes("Hello, World!");
                    await socket.SendAsync(new ArraySegment<byte>(msg, 0, msg.Length), WebSocketMessageType.Text, true, CancellationToken.None);
                    await Task.Delay(200);
                }
            });
    
            await Task.WhenAny(receiveTask, sendTask);
    
            Console.WriteLine("WebSocket connection closed.");
        }
    
        static void Main()
        {
            var builder = WebApplication.CreateBuilder();
            var app = builder.Build();
    
            app.UseWebSockets();
    
            app.Use(async (context, next) =>
            {
                if (context.Request.Path == "/ws")
                {
                    if (context.WebSockets.IsWebSocketRequest)
                    {
                        await ClientHandler(context);
                    }
                    else
                    {
                        context.Response.StatusCode = StatusCodes.Status400BadRequest;
                    }
                }
                else
                {
                    await next(context);
                }
            });
    
            app.Run("http://127.0.0.1:8001");
    // or app.Run("http://0.0.0.0:8001");
    
        }
    }
    

    Solution no.2: You can use Fleck library

    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();
    
            // Start Fleck WebSocket server
            StartWebSocketServer();
    
            host.Run();
        }
    
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    
        private static void StartWebSocketServer()
        {
            var server = new WebSocketServer("ws://0.0.0.0:8181");
    
            server.Start(socket =>
            {
                socket.OnOpen = () => Console.WriteLine("Client connected!");
                socket.OnClose = () => Console.WriteLine("Client disconnected!");
                socket.OnMessage = message =>
                {
                    Console.WriteLine("Received message: " + message);
                    socket.Send("Echo: " + message);
                };
            });
    
            Console.WriteLine("WebSocket server started on ws://0.0.0.0:8181");
        }
    }
    
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }
    
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
          
            app.UseRouting();
    
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });
        }
    }