LG202: IoT Cloud Server Project - WebSocket Server

As Wikipedia states, “WebSocket is a computer communications protocol, providing full-duplex communication channels over a single TCP connection…” indeed, and whilst you may berate my use of Wikipedia in a semi-academic article, anyone who uses Stack Exchange regularly knows, if there was anything inaccurate about this entry, it would have been pointed out!

The entry goes on… “The WebSocket protocol enables interaction between a web browser (or other client application) and a web server with lower overhead than half-duplex alternatives such as HTTP polling, facilitating real-time data transfer from and to the server.”

Perfect, that’s exactly what we need, I’ve developed some real-time applications using Microsoft SignalR with some success, and that’s really just an abstraction of WebSocket with some routing functionality built-in.

As I have a unique plan for routing messages, and there is limited support for SignalR across non-Microsoft environments, we’d end up hacking this into so many pieces we may as well start with good old unabstracted WebSocket Server.

WebSocket connections start with a handshake over HTTP requesting a protocol upgrade, this is how we will know which requests we need to handle. The basic functionality of the WebSocket server is handled by two files, I’ve annotated them in some detail and included them below, but here’s an overview of what they do. The starting point for my code came from Radu Matei’s blog post:

WSMiddleware.cs

I’ve already mentioned, and demonstrated, slightly narcissistically, the power of middleware. This is exactly how we filter off the Web-Socket requests and process them.

WSMiddleware is our custom middleware class that deals with Web-Socket requests, we use a static concurrent dictionary to store a list of devices that we use to route messages. The single instance of this object is available in memory to all threads and is constantly being updated to keep track of which devices are connected and which other devices they can communicate with.

app.MapWhen(context => context.Request.Path.StartsWithSegments("/ws"), appBuilder =>
{
      app.UseWebSockets();
      app.UseMiddleware();
});

The above code in the Configure method of the .net Core StartUp class tells our application to use our middleware for any requests sent to the /ws URL on our site.

The middleware then uses the Invoke(HttpContext context) method to process the request before handing it on to the next Request Delegate in the Pipeline.

So, for instance, the first thing we want to do is check if the request is, in fact, a WebSocket request… if this isn’t we want to pass it on straight away as it is of no interest to us.

if (!context.WebSockets.IsWebSocketRequest)
{
    await _next.Invoke(context);
    return;
}      

The middleware performs the following tasks when a new connection is requested:

  • Check request is web socket.
  • Checks an ID has been passed in the QueryString.
  • Checks the ID is known.
  • Opens a WebSocket connection.
  • Records its details in a Dictionary.

The middleware performs the following tasks when a message is received:

  • Checks what type of message it is.
  • Closes the connection if it’s a Close request.
  • Passes the Message on to WSManager if it’s a text message.

You can view the code here…

WSManager.cs

A WebSocket is a server/client communication channel, the Server opens the connection, awaits messages and can echo them back or reply to them, which is cool…

But we need to be able to route messages to other clients too, so we need to know which Sockets represent each client, and if they’re online or not.

By appending an ID to the WebSocket request we have a link between the socket and the user. The example I used as a basis for this development, and most other examples use a ConcurrentDictionary, which is a thread-safe version of a standard Dictionary, with the ID as the Key, and the WebSocket as the Value.

Now this works great, in our instance, we are going to regard everything that can request a WebSocket connection as a Device, and we are going to verify them, make them shake hands and generate a list of their roles in each of the projects they have access to. These devices are represented in the WSDevices.cs class, so let us add a WebSocket to that, and store that in the dictionary.

We can then access the WebSocket as a value of that class when we need to check or amend its status based on notifications from our Middleware..

public async Task SendMessageAsync(string message, string id)
{
     if (devices.ContainsKey(id))
     {
           if (devices[id].Socket.State == WebSocketState.Open)
           {
                await SendMessageAsync(devices[id].Socket, message);
           }
     }
}

The WSManager is the brains of our server and currently has the following commands:

  • Handshake
  • Verify Message
  • Route messages

… and saves the data to the database if the message type is Sensor Data.

We are going to go into these functions in more detail in the next couple of articles, but we now have a running Web Socket Server that can communicate with connected clients.

You can view the current code here, these functions will be expanded and refined as analytics highlight any areas we can improve upon.

Scalability

During early testing of the server, I broke the system with a single sensor sending data to a database twice a second… two months into the design and development my heart sank!

Regardless of the rate I sent the data, sooner or later it stopped working, the faster I sent it, the faster it stopped working… this indicated I was maxing out the connections.

Luckily, the test code I have written was shocking and wasn’t disposing of the HTTP connections to the database, I was running out before they reset themselves. Easy to fix with a simple ‘Using’ statement but it started me thinking about how quickly I might reach those limits in the real world.

but the recommended model is to maintain a single instance…

Hovering over the Database Client in my source code, offered a solution and even a link to an example… The Singleton pattern in the .Net Core Pipeline allows us to create instances of objects that we can pass to other objects when we invoke them.

In his article on Azure Web-App scalability Troy Hunt achieves about 380,000 requests a minute, on a pretty heavy database request… 6000 requests a second… half a billion data points a day isn’t too shabby for a starting point for a sensor data server.

Scaling past this is possible on azure as we can simply add multiple instances of any of the services in our architecture. This would require some alterations to the classes covered in this article.

There would be multiple instances of the Concurrent Dictionary and multiple Web Socket Servers, A global Cache of these Dictionaries and the ability to transfer messages between them could be handled by the inclusion of Redis Cache in the infrastructure… but that’s one for the future.

Now we have a mechanism to send messages, we need to decide what we are going to send…