Implementing WebSocket Client and Server on ASP.NET Core 6.0 (C#)

Kendrick
Bina Nusantara IT Division
9 min readJun 30, 2023

--

Photo by Marc-Olivier Jodoin on Unsplash

Websocket is a protocol that enables seamless communication between clients and servers over a single, long-lived connection. Unlike traditional web communication methods like HTTP, which follows a request-response model, WebSockets introduce a full-duplex communication channel. This means that both the client and server can send and receive data simultaneously, allowing for instant updates and real-time interaction.

Http vs Websocket (Source: Microsoft Learn)

WebSockets are commonly used in scenarios requiring real-time data updates. Examples of such situations include a multiplayer game’s lobby chat, real-time notifications, stock market data feeds, and live collaboration tools. These applications necessitate instantaneous communication and data exchange, something that WebSockets adeptly provides.

However, while WebSockets bring significant advantages, they do require specific server-side support. The server needs to implement a WebSocket server that adheres to the WebSocket protocol. This protocol includes handling incoming connections, managing client sessions, and routing messages between connected clients.

In this tutorial, we will implement a WebSocket server and client in .NET 6.0 with C#

Prerequisites

  1. Visual Studio 2022
  2. .NET 6.0

Creating a WebSocket Server

First, open Visual Studio and Create a new ASP.NET Core Project from the template. Pick the “ASP.NE Core Empty” template with the C# as the language.

If this option is unavailable, install “ASP.NET and web development” features from Visual Studio Installer.

Enter your project name, location, and solution name. I will use “SimpleWS-Server” as the project and solution name for our WebSocket Server.

Pick .NET 6.0 as the Framework. Leave everything else unmodified.

After the project is generated, we will get ourselves a blank ASP.NET Core 6.0 Template. We can delete the unused “Hello World!” endpoint. Modify app.Run() to app.RunAsync().

To use WebSocket in our project. We call the UseWebSockets() function on our WebApplication instance. For our convenience, we can also call builder.WebHost.UseUrls() method to set the URL and port for our server. In this tutorial, I use localhost as the URL with port 6969.

Then we need to map an endpoint to be used for our WebSocket Server. We will do this by using the app.Map() method with our route and context which we will use for handling WebSocket. Here we will use “ws” as the WebSocket route.

Inside the context, we will configure the endpoint to accept only WebSocket connections by implementing a check of context.WebSockets.IsWebSocketRequest. Then set the status code to BadRequest (400) if it is not a WebSocket request.

After checking the connection is a WebSocketRequest. We can use the following code to define the current connection instance.

using var ws = await context.WebSockets.AcceptWebSocketAsync();

With the WebSocket instance, we can check the connection State with ws.State.

Here are the possible values of WebSocketState
1. None
2. Connecting: The connection is negotiating the handshake with the remote endpoint.
3. Open: Initial state after the HTTP handshake has been completed.
4. CloseSent: Close message was sent to the client.
5. CloseReceived: Close message was received from the client.
6. Closed: Close handshake completed gracefully.
7. Aborted: The connection is aborted.

To send a message, we can use SendAsync() method on our WebSocket connection instance. The SendAsync method accepts ArraySegment<byte>, WebSocketMessageType enum, bool endofMessage, and CancelationToken as its parameter. To send a text to the client, we need to convert it into a byte array and create an ArraySegment<byte> with it.

var message = "Hello World!";
var bytes = Encoding.UTF8.GetBytes(message);
var arraySegment = new ArraySegment<byte>(bytes, 0, bytes.Length);
await ws.SendAsync(arraySegment,
WebSocketMessageType.Text,
true,
CancellationToken.None);

Now let’s make our WebSockets Server return the current time in “HH:mm:ss” format every 1 second to the client. We can do this by wrapping the logic that we use to send messages inside a while(true) loop and using Thread.Sleep(1000) to implement the 1-second delay. We will also check to only send the Message when the WebSocket State is Open and break the loop if the state is Closed or Aborted.

Voila, Our WebSocket server is done. Now to check it, we need a WebSocket Client.

Creating a WebSocket Client

First, open Visual Studio and Create a C# .NET Console App

Enter your project name, location, and solution name. I will use “SimpleWS-Server” as the project and solution name for our WebSocket Server.

Pick .NET 6.0 as the Framework

To create a WebSocket Client we can use the ClientWebSocket from System.Net.WebSockets.

using System.Net.WebSockets;
var ws = new ClientWebSocket();

To connect with a WebSocket server we can use the ConnectAsync method with the URL and Cancelation Token.

await ws.ConnectAsync(new Uri("ws://localhost:6969/ws"), 
CancellationToken.None);

Now let’s create a task to receive a message from the Server while the connection is alive.

To receive the message, we can use the ReceiveAsync() method on our WebSocketClient instance. The ReceiveAsync method accepts ArraySegment<byte> and CancellationToken. To receive a message, we need to create a new byte array as a buffer which we will use to create the ArraySegment<byte>. For CancellationToken, we will use CancellationToken.None.

Remember that the result is still in byte array, to display it as string. We need to Encode it first. We can do it by using the Encoding.UTF8.GetString() Method.

Lastly, we need to check if the connection is not closed before processing the result. We can do this by checking if the message type is not Close

result.MessageType == WebSocketMessageType.Close

Here is the final code for our WebSocket Client.

Now run our WebSocket Server and then Client. If it worked then our client should be displaying the current time every 1 second.

Voila, we can create a WebSocket Server which can send a message and a Client that can Receive the messages.

Is this tutorial done? NOT YET. We are yet to handle Multiple WebSocket Connections, sending messages from the Client, and receiving the Client’s message on the Server.

Handling Multiple WebSockets Connection

Let’s implement a simple chatroom application with WebSockets by modifying our previous code.

To keep track of Multiple WebSocket Connections in the WebSocket Server, we can store the WebSocket connection instance in a list.

To broadcast a message to every client, we can create a function that Calls the SendAsync method with every websocket instance that is open in the list

To emulate a chatroom, we will add a username for the connected client which is fetched via the request parameter. We will also broadcast whenever a client is connected.

Now, let’s handle the process of receiving messages. Similar to the Client Side, we can call the ReceiveAsync method with a buffer while the socket state is Open. Let’s wrap this logic into a function ReceiveMessage

Then call our ReceiveMessage function inside our WS handling code.

Now to broadcast the received message to other connected clients, we need to modify our ReceiveMessage function. Let’s add an Action<WebSocketReceiveResult, byte[]> handleMessage delegate in our ReceiveMessage function parameter. Call the handleMessage delegate by passing the result of ReceiveAsync and buffer.

Then modify the original ReceiveMessage call to pass a lambda function containing the delegate.

Inside the lambda function, we will broadcast the received text message from a client to every client. We can do this by calling our previous Broadcast function if the result’s message type is text.

What if we want to keep track of clients closing the connection and broadcasting it to everyone? First, we need to check if the message type is Close or if the Socket’s state is aborted. Then remove the closed WebSocket connection instance from our List. Then we will call the Broadcast method and CloseAsync method to close the connection gracefully.

The final code should look like this.

We are done with our WebSocket server. Now let’s modify our WebSocket client to handle sending messages to the server. First, Let’s add username input which will be used in the chatroom.

Create a Task to handle sending the message to the WebSocket server. Here we will read the input from user and send it by calling SendAsync on the ClientWebSocket instance. We will add a check to exit the chatroom with “exit”

We have both receive and message tasks running asynchronously. Finally, handle the client’s exit by sending CloseAsync whenever one of the threads is finished. Then await until all thread is finished to close the program.

Our simple chatroom application built with WebSocket is now done. Let’s run the server and the client. If we are connected successfully, we will get the “user joined the room” broadcast. Let’s try inputting a message to be sent.

Great, we can successfully send a message to the server. Now, Let’s spin up another instance of the client. If nothing goes wrong, we will get the “user joined the room” broadcast on all clients.

Let’s try sending a message on our second instance.

Now Let’s add another instance of client, and close the connection on the second client. If nothing is wrong, then we will get the 3rd client’s “user joined the room” broadcast on all clients and then the 2nd client’s “user left the room” broadcast for all the remaining clients.

Conclusion

In this article, we have explored the basics of WebSocket, discussed how to create both a WebSocket server and client, and shown how to handle common tasks such as sending and receiving messages, handling connection aborts, and broadcasting messages to all connected clients.

We’ve seen how this technology is a game-changer for real-time applications, allowing for seamless, bidirectional communication between clients and servers. This is expressed in our implementation of the real-time chat application.

The code for both the Server and Client can be viewed here.

References

  1. Websockets: https://www.wallarm.com/what/a-simple-explanation-of-what-a-websocket-is
  2. System.NET.Websockets: https://learn.microsoft.com/en-us/dotnet/api/system.net.websockets?view=net-6.0

--

--