Ctesiphon — Messaging Application using .NET Core , Redis and Websockets

Image for post
Image for post

Introduction

Ever since i started playing online games in middle-school back in 2003 i have been using messaging applications in order to communicate with my peers. The first such application which in time became ubiquitous was Skype.

While gaming was the main dish — i still remember the countless hours spent playing Warcraft 3 , Cs 1.6 , Dota etc — we were also using it to chat , have some after school fun and also discuss homeworks , share ideas etc.

Years after completely abandoning gaming and joining the developers guild , dabbling for some time in areas such as Industrial Automation , Embedded Devices i rediscovered my passion for chat applications , but this time i wanted to create them.

This is the purpose of this article. To show you how to write a chat application from scratch.

Note: For those that are interested in the source code you can find my repository on github with full comments,diagrams and explanations here.

Supported Features:

  • subscription to one or multiple chat rooms
  • unsubscription from target/all chat rooms
  • sending messages to target chat room
  • receiving messages from all subscribed chat rooms

Important !!!!!!!!!!

This will be a lengthy article so for those that want to digest it step by step you can find it in in github repository here or as a pdf here .

Architecture

  • Server: ASP NET Core Web application — the server where our logic will run handling client operations (subscribe/unsubscribe/publish message/get channels)
  • Database : Redis as a message broker with its publish/subscribe functionality and also for storage (user subscribed channels)
  • Client Communication Protocol : Since this is a chat application (bidirectional communication required) , the protocol we will be using is Websockets.

Flow

By flow we will be referring to the way both inbound- messages arriving from the client and outbound messages sent to the client are handled and where and how does the Websocket object fit in as well as the Redis database.

Inbound Task

The inbound a Task is basically a loop which receives messages from the client , parses them and dispatches them to an appropriate handler as can be seen from above.

Note: One such handler will write messages to a redis channel.

Outbound Task

The outbound Task is a loop started asynchronously from the inbound task.Its purpose is to pop items off the queue and write them over the websocket to the connected client.

The Outbound Queue

The outbound queue acts as a sink for all producers as can be seen from the picture. In our case the producers are:

  • Inbound Task: The messages that the server sends back to the client (messages of type SERVER_RESULT)
  • Redis: All messages that are published on channels on which our user is subscribed to.

As long as there are messages available in the queue we pop them and send them to the client over the websocket connection.

When there are no messages inside the queue, the task blocks , awaiting new ones.

Implementation & Source Code

We will be starting our project from a template of type ASP NET Core Web Application`.

Main

Startup

The above sections are mandatory in any ASP NET Core application and are automatically generated from the template .

The Program.Main starts the application and will use the `Startup` class to configure it.

In the Startup.ConfigureServices , the ConnectionMultiplexer is our connection to the redis database and will be injected as a singleton resource in our application. Connected clients will use the multiplexer as a factory for subscriptions to target channels.

Middleware

In the `Startup` class `Configure` method above , notice the `MapWhen`extension . Based on a predicate it will route all requests to the specified ASP NET Core Middleware.

In our case:

  • predicate = request should be of type websocket
  • middleware is of type `SocketWare` (presented below)

The ConnectionMultiplexer is passed using dependency injection — remember it was injected in the Startup.ConfigureServices method !

The Invoke method is a minimal requirement for any ASP NET Core Middleware so that the framework knows to route the incoming request , and lets you handle it.

Core

This is the core of the application and since it is the most complex part i will post the entire component , and will explain it afterwards.

State

The core component uses a private field of type State for its operations.

  • ISubscriber is a component of StackExchangeRedis and is used to subscribe/unsubscribe on redis channels.
  • IDatabase is a also a component of the StackExchangeRedis and is used for issuing redis querries.
  • outboundTask — the task that runs the outbound flow ( taking messages from the queue and pushing them over the websocket)

Chat Client

The RunAsync is the entrypoint to our client ; it starts the asynchronous outboundTask and then continues with running the inbound task.

When the inboundTask is finished/throws exception , we run the CleanupSessionAsync which deletes channel subscriptions as well as client data stored in a redis hashset.

The OnRedisMessageHandler delegate adds new messages to the outboundQueue and is needed to be provided when subscribing/unsubscribing on a target channel.

The outboundTask is a long running loop , that pops messages off the outboundQueue or waits in case of none present (blocking operation) ; the popped message is then written over the websocket to the client.

Note : To better understand the scope of the two tasks check out the Sequence Diagram .

Chat Client Handlers

Now that all I/O operation logic has been written we are going to define the message types and their handlers:

Message Types

Note — All messages that travel over the websocket both inbound and outbound comply to the format of WSMessage

Dispatcher

Whenever a new inbound message arrives we call the below method.

Depending on the WSMessage Kind we will deserialize the WSMessage Payload in a Control Message or a ChatMessage.

Next we are going to see how each message is being treated. The source file cand be found here as well .

Susbscribe Handler

In order for the client to subscribe to a channel he must provide a `ClientId` as well as a ChanneId .

For each client we store in redis an associated hashset of the form :

We will first test if the ClientId matches with the one in redis.If positive we will test if the target Channel is already present in the redis hashset.

If all is fine we will use the SubscribeAsync method for the target `Channel` providing the OnRedisMessageHandler as the required argument.

On any type of failure we will write in the outboundQueue a SERVER_RESULT type of message with the specific error message.

Unsubscribe Handler

We delete the hashset associated with the `ClientId` and then use UnsubscribeAsync providing the OnRedisMessageHandler.

The result of the operation is written to the `outboundQueue`.

Chat Message Handler

If the client is subscribed to the target channel ( the channel name exists in the redis hashset) we publish the message to redis.

Otherwise we write the failed attempt to the `outboundQueue`.

Get Channels Handler

The dispatcher as well as the handlers can be found here

Prerequisites

NET 5.0

For this application we are using .NET 5.0 and you can download it from [here](https://dotnet.microsoft.com/download/dotnet/5.0).

Redis

Installing

For this solution you will need to install Redis Server . You can download it from here.

For windows users (me included) the easiest way to install redis is via the package manager chocolatey from here. Once installed from a terminal just run:

If the install was successful from a terminal run

and you should see the below output which indicates your redis server is up and running..

Redis-Cli

With the `redis-server` started you can start playing with redis using the `Redis-Cli` from a terminal with the command:

Start having fun with redis by trying its Commands !

Testing

I will be using Simple WebSocket Client as a testing interface.

Subscribe

Unsubscribe

Message

Now that you are comfortable with the Simple WebSocket Client we can try sending messages to ourselves like below:

We subscribe to channel `mychannel`, we send some messages and then unsubscribe. As expected the last message will not get published since we unsubscribed from the target channel.

Note:

For debugging/diagnosing purposes you can open Redis-Cli and via the Pub/Sub functionality , subscribe to the target Channel and see what messages are flowing through it , or push messages directly and see if they arrive in your Simple WebSocket Client . (Make sure you send only serialized WSMessage.

Further developments

In this article we have developed the server of a chat application.

We have tested the application by using a simple websocket client as an extension of google chrome but as you might have already guessed , for any non-trivial scenario in which this application is going to be used , we are going to need a dedicated Chat Client.

This is precisely what the next article will be about. Stay tuned !

Developer , hardcore reader , calisthenics enthusiast with a passion for history and civilisation.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store