Close

Web Sockets versus TCP Sockets

At my client there was a requirement to integrate a Warehouse Management System (WMS) with a Warehouse Automation Solution (WCS) with the following requirements:

  • WMS integrates via a SOAP Service.
  • WCS uses specific ports, for example ports 40001 (WMS2WCS) en 40011 (WCS2WMS).
  • WCS requires real-time communication via a long-running connection. WCS can send Keep Alive messages at a regular interval.
  • WCS sends ack/nack after message validation.

Although the above requirements clearly point to a TCP socket solution, we have proposed to use websockets. The reason is that we want to use an Azure PaaS service. Using a PaaS service, means that Microsoft is responsible for the underlying infrastructure. That implies that low-level communication at the transport level – as required by the tcp protocol – is not possible in PaaS. Neither does PaaS offer static ip addresses by nature.

The PaaS service we want to use is named Azue Web PubSub. The Web PubSub service can be used for real-time messaging applications using web sockets and the publish-subscribe pattern. We use subprotocol json.webpubsub.azure.v1 to exchange messages in JSON format. The actual xml message is a passed as a string via the data node.

High level solution overview:

Web sockets operate at the application layer, TCP sockets operate at the transport layer. With web sockets we can send text messages instead of raw bytes. TCP sockets are just abstracted away for simplicity. With web sockets, you initiate a connection over HTTP and next upgrade this connection to use the web sockets protocol. The Http protocol operates at port 80/443. You can’t use other ports in Azure Web PubSub, Azure App Services or any other PaaS service.

The Web PubSub service uses the concept of a hub. The hub immediately sends an ack on receiving the message. It’s not possible to validate the incoming message before sending an ack/nack. The Web SubSub service is high available by nature. The connection is kept open without having to send keep alive messages.

And as a last benefit, Azure Web PubSub – as the name implies – implements a publish-subscribe mechanism. That is one of the differences with SignalR. A SignalR solution uses the web protocol by default (with fallback to other solutions such as long polling), but doesn’t implement the pub-sub protocol. All very easy to implement. Note though, that Azure Web Pub Sub is still in preview at the time (preview since Sept 2021).

If we use tcp sockets, we still have to implement a hub. In the hub we have to define a buffer to receive messageas as a byte stream. Messages are read byte-by-byte. Each message starts with an ASCII encoded start tag and ends with an ASCII encoded end tag. That’s important to understand. Although the messages are sent one by one, they are arrive at the hub as a byte stream. We need start and end tags to separate the messages.

Let’s conclude our discussion with a code sample of the WMS client and the WCS client in a Web PubSub solution:

WMS Client:
NuGet Azure.Messaging.WebPubSub, Websocket.Client

using Azure.Messaging.WebPubSub;
using System;
using System.Net.WebSockets;
using System.Text.Json;
using System.Threading.Tasks;
using Websocket.Client;

namespace WMSPubSub
{
    class WMSClient
    {
        static async Task Main(string[] args)
        {

            Console.WriteLine("WMS");

            var connectionString = "Endpoint=https://[client].webpubsub.azure.com;AccessKey=...;Version=1.0;";
            var hub = "[client]hub";
            int ackId = 1;

            // Either generate the URL or fetch it from server or fetch a temp one from the portal
            var serviceClient = new WebPubSubServiceClient(connectionString, hub);
            var url = serviceClient.GenerateClientAccessUri(userId: "wmsuser", roles: new string[] { "webpubsub.joinLeaveGroup.wcs2wms", "webpubsub.sendToGroup.wms2wcs" });

            using (var client = new WebsocketClient(url, () =>
            {
                var inner = new ClientWebSocket();
                inner.Options.AddSubProtocol("json.webpubsub.azure.v1");
                return inner;
            }))

            {

                // Connect to hub
                // Disable the auto disconnect and reconnect because the sample would like the client to stay online even no data comes in
                client.ReconnectTimeout = null;
                Action<ResponseMessage, int> ProcessMessageDelegate = ProcessMessage;
                client.MessageReceived.Subscribe(msg => ProcessMessageDelegate(msg, ackId-1));
                await client.Start();
                Console.WriteLine("Connected to hub");

                // Subscribe to channel wcs2wms
                client.Send(JsonSerializer.Serialize(new
                {
                    type = "joinGroup",
                    group = "wcs2wms"
                }));
                Console.WriteLine("Subscribed to channel wcs2wms");

                // Send Message
                var streaming = Console.ReadLine();
                while (streaming != null)
                {
                    if (!streaming.Contains("subuser"))
                    {
                        client.Send(JsonSerializer.Serialize(new
                        {
                            type = "sendToGroup",
                            group = "wms2wcs",
                            dataType = "text",
                            data = streaming,
                            ackId = ackId++
                        }));

                    }
                    streaming = Console.ReadLine();
                }

                Console.WriteLine("done");
            }
        }

        static void ProcessMessage(ResponseMessage msg, int ackId)
        {
            Console.WriteLine($"ackId: {ackId.ToString()}");
            Console.WriteLine($"Message: {msg}");
        }

    }
}

WCS Client:

using System;
using System.Net.WebSockets;
using System.Text.Json;
using System.Threading.Tasks;

using Azure.Messaging.WebPubSub;

using Websocket.Client;

namespace WCSPubSub
{
    class WCSClient
    {
        static async Task Main(string[] args)
        {

            Console.WriteLine("WCS");

            var connectionString = "Endpoint=https://[client].webpubsub.azure.com;AccessKey=...;Version=1.0;";
            var hub = "[client]hub";
            int ackId = 1;

            // Either generate the URL or fetch it from server or fetch a temp one from the portal
            var serviceClient = new WebPubSubServiceClient(connectionString, hub);
            var url = serviceClient.GenerateClientAccessUri(userId: "wcsuser", roles: new string[] { "webpubsub.joinLeaveGroup.wms2wcs", "webpubsub.sendToGroup.wcs2wms" });

            using (var client = new WebsocketClient(url, () =>
            {
                var inner = new ClientWebSocket();
                inner.Options.AddSubProtocol("json.webpubsub.azure.v1");
                return inner;
            }))

            {

                // Connect to hub
                // Disable the auto disconnect and reconnect because the sample would like the client to stay online even no data comes in
                client.ReconnectTimeout = null;
                Action<ResponseMessage, int> ProcessMessageDelegate = ProcessMessage;
                client.MessageReceived.Subscribe(msg => ProcessMessageDelegate(msg, ackId-1));
                await client.Start();
                Console.WriteLine("Connected to hub");

                // Subscribe to channel wms2cms
                client.Send(JsonSerializer.Serialize(new
                {
                    type = "joinGroup",
                    group = "wms2wcs"
                }));
                Console.WriteLine("Subscribed to channel wms2cms");

                // Send Message
                var streaming = Console.ReadLine();

                while (streaming != null)
                {

                    if (!streaming.Contains("pubuser"))
                    {
                        client.Send(JsonSerializer.Serialize(new
                        {
                            type = "sendToGroup",
                            group = "wcs2wms",
                            dataType = "text",
                            data = streaming,
                            ackId = ackId++
                        }));
                    }
                    streaming = Console.ReadLine();
                }

                Console.WriteLine("done");


                //Console.Read();
            }
        }

        static void ProcessMessage(ResponseMessage msg, int ackId)
        {
            Console.WriteLine($"ackId: {ackId.ToString()}");
            Console.WriteLine($"Message: {msg}");
        }

    }
}