How to use websockets in asp.net core
Asked Answered
I

2

12

im trying to develop a game where i store a scoreboard in a text file which is stored on server (currently on localhost). I am using http get and post calls in order to communicate with the server and get and send the data that i want. Now i want to implement websockets in order to send a notification from the server to the c# client. The notification will just display on the console a message for the user, for example in mu case i want to display a message to the user each time a user is added to the scoreboard, each time the UpdateScoreBoard method is called. Based on tutorials i found online i have managed to build the following code, can anyone make it more clear for me how the i will build the websocket for the server and how i will initialize the websocket on the client? Thank you

Startup.cs (Server)

        public void Configure(IApplicationBuilder app, IHostEnvironment env)
        {
          //deleted code

            var webSocketOptions = new WebSocketOptions()
            {
                KeepAliveInterval = TimeSpan.FromSeconds(120),
                ReceiveBufferSize = 4 * 1024
            };


            app.UseWebSockets(webSocketOptions);
        
            app.Use(async (context, next) =>
            {
                if (context.Request.Path == "/ws")
                {
                    if (context.WebSockets.IsWebSocketRequest)
                    {
                        WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
                        await Echo(context, webSocket);
                    }
                    else
                    {
                        context.Response.StatusCode = 400;
                    }
                }
                else
                {
                    await next();
                }

            });
        }

        private async Task Echo(HttpContext context, WebSocket webSocket)
        {
            var buffer = new byte[1024 * 4];
            WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            while (!result.CloseStatus.HasValue)
            {
                await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);

                result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            }
            await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
        }

HttpClass.cs (Client) - where i call the http post request

public async override Task<List<Scoreboard>> UpdateScoreBoards(string username, int attempts, int seconds, DateTime date)
            {
                HttpResponseMessage response = null;
                //Creating a new instance of object Scoreboard
                //deleted code

                var url = "http://localhost:5000/api/Scoreboard";

                var socket_url = new Uri("ws://localhost:5000"); 
                var exitEvent = new ManualResetEvent(false);
                using (var client = new WebsocketClient(socket_url))
                {
                    client.ReconnectTimeout = TimeSpan.FromSeconds(30);
                    client.ReconnectionHappened.Subscribe(info =>
                        Log.Information($"Reconnection happened, type: {info.Type}"));

                    client.MessageReceived.Subscribe(msg => Log.Information($"Message received: {msg}"));
                    await client.Start();

                    await Task.Run(() => client.Send("test"));

                    exitEvent.WaitOne();
                }

// deleted code
            }
Idolatrize answered 6/7, 2020 at 13:12 Comment(0)
S
9

can anyone make it more clear for me how the i will build the websocket for the server and how i will initialize the websocket on the client?

As the example that you referenced demonstrated, making use of WebSocket in ASP.NET Core, we can add the WebSockets middleware in the Configure method, then add/configure request delegate to check and handle incoming WebSocket requests.

And after transitioned a request to a WebSocket connection with AcceptWebSocketAsync() method, we can use the returned WebSocket object to send and receive messages.

In Echo method, we can also perform custom code logic to generate and send reply message/notification based on received message(s).

//received message
var mes = Encoding.UTF8.GetString(buffer, 0, result.Count);

//code logic here
//...

//create reply message
var reply_mes = $"You sent {mes}.";

byte[] reply_mes_buffer = Encoding.UTF8.GetBytes(reply_mes);

await webSocket.SendAsync(new ArraySegment<byte>(reply_mes_buffer, 0, reply_mes.Length), result.MessageType, result.EndOfMessage, CancellationToken.None);

Besides, ASP.NET Core SignalR is an open-source library that simplifies implementing real-time communication functionality. And it does support WebSockets transport and we can easily achieving push messages/notifications to all connected clients or specified subsets of connected clients.

For more information about ASP.NET Core SignalR, you can check this doc: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/websockets?view=aspnetcore-3.1

Sartre answered 7/7, 2020 at 9:41 Comment(0)
U
8

The only thing you need in your Startup is to add the UseWebsockets middleware. Then you can define your own middleware and filter connections if they are websocket type like below:

Startup

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
            app.UseWebSockets();
            app.UseMiddleware<SocketWare>();
        }

Middleware

public class SocketWare {
        private RequestDelegate next;
        public SocketWare(RequestDelegate _next) {
            this.next = _next;
        }
        public async Task Invoke(HttpContext context) {
            if (!context.WebSockets.IsWebSocketRequest) {
                return;
            }
            var socket=await context.WebSockets.AcceptWebSocketAsync();
            await RunAsync(socket);
        }
        private async Task RunAsync(WebSocket socket) {
            try {
                var client = new ChatClient(socket);
                await client.RunAsync();
            } catch (Exception ex) {

                throw;
            }
            
        }
        

    }

In my middleware i prefer to keep my business logic in a separate class that gets the Websocket injected in it like below:

Client

public class ChatClient
{
   private Task writeTask;
   private Task readTask;
   private WebSocket socket;
   private CancellationTokenSource cts=new CancellationTokenSource();
   ChatClient(WebSocket socket)
   {
       this.socket=socket;
   }
   public async Task RunAsync()
   {
      this.readTask=Task.Run(async ()=>await ReadLoopAsync(cts.Token),cts.Token);
      this.writeTask=Task.Run(async()=>await WriteLoopAsync(cts.Token),cts.Token);
      await Task.WhenAny(this.readTask,this.writeTask);
   }
   public async Task WriteLoopAsync()
   {
       Memory<byte> buffer=ArrayPool<byte>.Shared.Rent(1024);
       try {
           while (true) {
              var result= await this.socket.ReceiveAsync(buffer,....);
              var usefulBuffer=buffer.Slice(0,result.Count).ToArray();
              var raw=Encoding.Utf8.GetString(usefulBuffer);
              //deserialize it to whatever you need
              //handle message as you please (store it somwhere whatever)
            }
        } catch (Exception ex) {

               //socket error handling
               //break loop or continue with go to
        }
   }
   public async Task ReadLoopAsync()
   {
          try {
            while (true) {
              
                var data = await this.[someMessageProvider].GetMessageAsync() //read below !!!
                var bytes = Encoding.UTF8.GetBytes(data);
                //send the message on the websocket
                await this.socket.SendAsync(data, WebSocketMessageType.Text, true, CancellationToken.None);
            }
        } catch (Exception ex) {

            //do incorrect message/socket disconnect logic
        }
   }
}

Now regarding producing messages and consuming them. In your case you could define your producers as some Controller routes like below .You would hit a route , produce a message and publish it to some message broker. I would use a Message Queue (RabbitMQ) or even a Redis Pub/Sub as a message bus. You would publish messages from your route(s) and then consume them in your ReadLoopAsync method from WebSocketClient (look above).

Producing messages

public UpdateController:Controller
{
   private IConnection
   [HttpPost]
   [someroute]
   public void UpdateScoreboard(string someMessage)
   {
       this.connection.Publish("someChannel",someMessage);
   }
   [HttpPost]
   [someotherroute]
   public void DeletePlayer(string someOtherMessage)
   {
       this.connection.Publish("someChannel",someMessage);
   }
}
  • Redis pub/sub
    Check redis pub/sub here
    Also check my repository on github here in which i am using exactly what you need (websockets, redis,pub sub)

  • RabbitMq
    Another option as a message bus is to use RabbitMQ , for more info regarding C# API here

  • In Memory

    You could also avoid using a third party and use some in memory data structure like a BlockingCollection.You could inject it as a Singleton service both in your Controller(s) and your socket Middleware(s)

Ultramontanism answered 17/7, 2020 at 8:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.