Search This Blog

Tuesday, March 29, 2016

Discovering Service within Network

A simple example showing how to implement a mechanism allowing clients to connect a service within a network if clients do not know the service address.
The example can be downloaded from here.

Introduction

The example bellow implements a client-service communication where the client needs to connect a service but it does not know the service address. Therefore before connecting the client needs a mechanism to find the service address out. The example bellow demonstrates this mechanism using UDP multicast messaging.

To Run Example

  1. Download this example.
  2. Open the ServiceDiscovery solution in Visual Studio.
  3. If your Visual Studio supports Nuget then just compile projects within the solution.
    (Eneter library will be downloaded automatically from the nuget.org server.)
    Otherwise download and unzip Eneter Messaging Framework for .NET platforms and update references to Eneter.Messaging.Framework.dll in Visual Studio projects.
  4. Run DiscoverableService.
  5. Run CalculatorClient.

UDP Multicast Messaging

The UDP protocol supports unicast, multicast and broadcast messaging. The unicast messaging is routing a message (datagram) to one particular host. The multicast messaging is routing a message to set of hosts (i.e. hosts which are registered within the particular multicast group). And broadcast messaging is routing a message to all hosts.
The capability of sending one message to multiple recipients at the same time can be effectively used to find out the service address within the network. If a client needs to connect the service first it can send a broadcast or a multicast UDP message into the network requesting the service address. The service (running on some device within the network) can so receive that request and send back its address. The received address can be then used by the client to open the connection with the service.

In order to optimize the overhead on hosts which do not provide the searched service it is preferred to use multicast instead of broadcast messaging.
In case of UDP multicast messaging the receiver needs to join a mulitcast group which is an IP address from the range 224.0.0.0 to 239.255.255.255 (the range from 224.0.0.0 - 224.0.0.255 is reserved for low-level routing protocol and you should not use it). By joining that multicast group the host sets its network interface to receive frames sent to that multicast IP address. It means if e.g. some process joins the multicast group 234.4.5.6 the host sets its network interface to receive and handle frames which are sent to the IP address 234.4.5.6. So if somebody then sends a message (UDP datagram) to the IP address 234.4.5.6 it is sent across the network and handled by hosts which has joined that group (and ignored by hosts which has not joined that group).



Service Application

The service application is a simple console application providing the 'rendezvous' service and the simple calculation service.
The rendezvous service listens to the UDP multicast group 234.4.5.6 and when it receives a request message it sends back the IP address and the port of the calculation service. When the client receives the address it can then open the connection to the calculation service and use it to calculate numbers.

The whole implementation is very simple:

using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.MessagingSystems.ConnectionProtocols;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;
using Eneter.Messaging.MessagingSystems.UdpMessagingSystem;
using System;

namespace DiscoverableService
{
    public class RequestMessage
    {
        public int Number1 { get; set; }
        public int Number2 { get; set; }
    }
    public class ResponseMessage
    {
        public int Result { get; set; }
    }

    class Program
    {
        private static string myServiceId = "My.Calculator.Service";
        private static string myServiceAddress = "tcp://127.0.0.1:8044/";
        private static IDuplexTypedMessageReceiver<ResponseMessage, RequestMessage> myServiceReceiver;
        private static IDuplexTypedMessageReceiver<string, string> myRendezvousReceiver;

        static void Main(string[] args)
        {
            IDuplexTypedMessagesFactory aReceiverFactory = new DuplexTypedMessagesFactory();

            // Create service to calculate numbers.
            // Note: This service will be possible to discover.
            myServiceReceiver =
                aReceiverFactory.CreateDuplexTypedMessageReceiver<ResponseMessage, RequestMessage>();
            myServiceReceiver.MessageReceived += OnServiceMessageReceived;
            
            TcpMessagingSystemFactory aTcpMessaging = new TcpMessagingSystemFactory();
            IDuplexInputChannel aServiceInputChannel =
                aTcpMessaging.CreateDuplexInputChannel(myServiceAddress);
            myServiceReceiver.AttachDuplexInputChannel(aServiceInputChannel);
            Console.WriteLine("Calculator service is listening to " + myServiceAddress);


            // Create the rendezvous service which receives requests to provide
            // the TCP address address of the calculator service.
            // It listens to the UDP multicast messages. Therefore everybody who needs
            // to connect the calculator service can just send the multicat message into
            // the network. The rendezvous service will receive it and sends
            // back the TCP address of the calculator service. 
            myRendezvousReceiver =
                aReceiverFactory.CreateDuplexTypedMessageReceiver<string, string>();
            myRendezvousReceiver.MessageReceived += OnRendezvousMessageReceived;

            UdpMessagingSystemFactory aUdpMessaging = new UdpMessagingSystemFactory(new EasyProtocolFormatter())
            {
                // The communication will be multicast
                UnicastCommunication = false,

                // This multicast group shall be joined.
                MulticastGroupToReceive = "234.4.5.6"
            };

            // Start listen to 127.0.0.1:8045 as well as to the configured
            // multicast group 234.4.5.6 on the port 8045.
            IDuplexInputChannel aRendezvousInputChannel =
                aUdpMessaging.CreateDuplexInputChannel("udp://127.0.0.1:8045/");
            myRendezvousReceiver.AttachDuplexInputChannel(aRendezvousInputChannel);
            Console.WriteLine("Rendezvous service is listening.");

            Console.WriteLine("Press ENTER to stop.");
            Console.ReadLine();

            myRendezvousReceiver.DetachDuplexInputChannel();
            myServiceReceiver.DetachDuplexInputChannel();
        }

        // It is called when a client requests the rendezvous service to provide the TCP address.
        static void OnRendezvousMessageReceived(object sender,
            TypedRequestReceivedEventArgs<string> e)
        {
            if (e.RequestMessage == myServiceId)
            {
                myRendezvousReceiver.SendResponseMessage(e.ResponseReceiverId, myServiceAddress);
            }
        }

        // It is called when a client requests to calculate numbers.
        private static void OnServiceMessageReceived(object sender,
            TypedRequestReceivedEventArgs<RequestMessage> e)
        {
            ResponseMessage aResponseMessage = new ResponseMessage();
            aResponseMessage.Result = e.RequestMessage.Number1 + e.RequestMessage.Number2;

            myServiceReceiver.SendResponseMessage(e.ResponseReceiverId, aResponseMessage);
        }
    }
}

Client Application

The client application is a simple WinForm application which sends the request message to the  UDP multicast group 234.4.5.6 and waits until the rendezvous service returns the IP address and the port for the calculation service. Then it uses the received address to connect directly the calculation service.

The whole implementation is here:

using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.MessagingSystems.ConnectionProtocols;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;
using Eneter.Messaging.MessagingSystems.UdpMessagingSystem;
using Eneter.Messaging.Threading.Dispatching;
using System;
using System.Windows.Forms;

namespace CalculatorClientSync
{
    public partial class Form1 : Form
    {
        // Request message.
        public class RequestMessage
        {
            public int Number1 { get; set; }
            public int Number2 { get; set; }
        }

        // Response message.
        public class ResponseMessage
        {
            public int Result { get; set; }
        }

        private IDuplexTypedMessageSender<ResponseMessage, RequestMessage> mySender;

        public Form1()
        {
            InitializeComponent();

            OpenConnection();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            CloseConnection();
        }

        private void OpenConnection()
        {
            IDuplexTypedMessagesFactory aSenderFactory = new DuplexTypedMessagesFactory()
            {
                // Timeout for synchrouse requests.
                SyncResponseReceiveTimeout = TimeSpan.FromMilliseconds(5000)
            };

            // Create UDP client which uses UDP multicast message to retrieve
            // the TCP address of the calculator service.
            ISyncDuplexTypedMessageSender<string, string> aRendezvousSender =
                aSenderFactory.CreateSyncDuplexTypedMessageSender<string, string>();
            UdpMessagingSystemFactory aUdpMessaging =
                new UdpMessagingSystemFactory(new EasyProtocolFormatter())
            {
                UnicastCommunication = false
            };
            
            // Create output channel which sends messages to the multicast group.
            IDuplexOutputChannel aUdpOutputChannel =
                aUdpMessaging.CreateDuplexOutputChannel("udp://234.4.5.6:8045/");
            aRendezvousSender.AttachDuplexOutputChannel(aUdpOutputChannel);

            // Send the request message to the multicast group
            // to retrieve the TCP address of the calculator service.
            // Once the message reaches the rendezvous service (listening to
            // the same multicast group) it will response the TCP address of the service.
            // If the resposne does not come wihtin the specified timeout (5 seconds)
            // the timeout exception is thrown.
            string aCalculatorServiceAddress = null;
            try
            {
                aCalculatorServiceAddress =
                   aRendezvousSender.SendRequestMessage("My.Calculator.Service");
            }
            finally
            {
                // Rendezvous service is not needed anymore.
                // So detach the output channel and release the thread lisening
                // to response messages.
                aRendezvousSender.DetachDuplexOutputChannel();
            }
            Console.WriteLine("Service address: " + aCalculatorServiceAddress);



            // Connect the calculator service on the received TCP address.
            IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory()
            {
                // Response messages will be routed to the main UI thread.
                OutputChannelThreading = new WinFormsDispatching(this)
            };
            IDuplexOutputChannel anOutputChannel = 
                aMessaging.CreateDuplexOutputChannel(aCalculatorServiceAddress);

            // Attach the output channel and be able to send messages
            // and receive response messages.
            mySender =
                aSenderFactory.CreateDuplexTypedMessageSender<ResponseMessage, RequestMessage>();
            mySender.ResponseReceived += OnResponseFromCalculatorReceived;
            mySender.AttachDuplexOutputChannel(anOutputChannel);
        }

        

        private void CloseConnection()
        {
            // Detach input channel and stop listening to response messages.
            mySender.DetachDuplexOutputChannel();
        }

        private void CalculateBtn_Click(object sender, EventArgs e)
        {
            // Create the request message.
            RequestMessage aRequest = new RequestMessage();
            aRequest.Number1 = int.Parse(Number1TextBox.Text);
            aRequest.Number2 = int.Parse(Number2TextBox.Text);

            // Send request to the service to calculate 2 numbers.
            mySender.SendRequestMessage(aRequest);
        }

        private void OnResponseFromCalculatorReceived(object sender,
            TypedResponseReceivedEventArgs<ResponseMessage> e)
        {
            // Display received result.
            ResultTextBox.Text = e.ResponseMessage.Result.ToString();
        }
    }
}

No comments:

Post a Comment