Search This Blog

Sunday, May 15, 2011

Service Listening to TCP, HTTP and Named Pipe at the same Time

Summary: A simple example showing how to implement a service listening to TCP, HTTP and Named Pipe at the same time.


The example below implements a simple service that is able to receive requests via TCP, HTTP and Named Pipe. Therefore, the service can be available for clients using different communications. E.g. Windows Phone 7 environment supports only HTTP.

The example is based on the Eneter Messaging Framework 2.0 that provides components for various communication scenarios.
(Full, not limited and for non-commercial usage free version of the framework can be downloaded from http://www.eneter.net. The online help for developers can be found at http://www.eneter.net/OnlineHelp/EneterMessagingFramework/Index.html.)


Multiple Listening
To implement the multiple listener, we will use the Dispatcher component from the Eneter Messaging Framework.
The Dispatcher receives messages from all attached input channels and forwards them to all attached output channels.
In our scenario, the dispatcher will have three input channels (TCP, HTTP and Named Pipe) and only one output channel (local channel to the message receiver).
So, if a message is received e.g. via the TCP input channel, the dispatcher will forward it through the output channel to the typed message receiver. The typed message receiver then notifies the user code implementing the service.










Multiple Listening Service Application
The service is implemented as a simple console application. The most important part is using of the dispatcher component for the multiple listening (TCP, HTTP and Named Pipe).
Since the service listens also to HTTP, you must execute it under sufficient user rights. (I recommend administrator for the debug purposes.)
The whole implementation is very simple.

using System;
using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.MessagingSystems.HttpMessagingSystem;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.NamedPipeMessagingSystem;
using Eneter.Messaging.MessagingSystems.SynchronousMessagingSystem;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;
using Eneter.Messaging.Nodes.Dispatcher;

namespace MultiReceivingService
{
    public class RequestData
    {
        public int Number1 { get; set; }
        public int Number2 { get; set; }
    }

    class Program
    {
        // Receiver receiving 'RequestData' and responding 'int'.
        // Note: Duplex typed message receiver can receive messages of specified type
        //       and send response messages of specified type.
        private static IDuplexTypedMessageReceiver<int, RequestData> myReceiver;

        static void Main(string[] args)
        {
            // Create local messaging connecting the dispatcher with the receiver.
            IMessagingSystemFactory aLocalMessaging = new SynchronousMessagingSystemFactory();
            IDuplexInputChannel aLocalInputChannel =
                aLocalMessaging.CreateDuplexInputChannel("MyLocalAddress");

            IDuplexTypedMessagesFactory aTypedMessagesFactory = new DuplexTypedMessagesFactory();
            myReceiver = aTypedMessagesFactory.CreateDuplexTypedMessageReceiver<int, RequestData>();
            myReceiver.MessageReceived += OnMessageReceived;

            // Attach the local input channel to the receiver and start to receive messages.
            myReceiver.AttachDuplexInputChannel(aLocalInputChannel);


            // Create messaging using HTTP.
            IMessagingSystemFactory anHttpMessaging = new HttpMessagingSystemFactory();
            IDuplexInputChannel anHttpInputChannel =
                anHttpMessaging.CreateDuplexInputChannel("http://127.0.0.1:8035/MyService/");

            // Create messaging using TCP.
            IMessagingSystemFactory aTcpMessaging = new TcpMessagingSystemFactory();
            IDuplexInputChannel aTcpInputChannel =
                aTcpMessaging.CreateDuplexInputChannel("tcp://127.0.0.1:8036/");

            // Create messaging using Named Pipe.
            // Note: Do not use '/' at the end of the named pipe address.
            IMessagingSystemFactory aPipeMessaging = new NamedPipeMessagingSystemFactory();
            IDuplexInputChannel aPipeInputChannel =
                aPipeMessaging.CreateDuplexInputChannel("net.pipe://127.0.0.1/MyService");


            // Create dispatcher that will receive messages via HTTP, TCP and Named Pipe
            // and forward them to the local address "MyLocalAddress" -> i.e. to our receiver.
            IDuplexDispatcherFactory aDispatcherFactory = new DuplexDispatcherFactory(aLocalMessaging);
            IDuplexDispatcher aDispatcher = aDispatcherFactory.CreateDuplexDispatcher();
            aDispatcher.AddDuplexOutputChannel("MyLocalAddress");

            aDispatcher.AttachDuplexInputChannel(anHttpInputChannel);
            Console.WriteLine("Listening to HTTP.");

            aDispatcher.AttachDuplexInputChannel(aTcpInputChannel);
            Console.WriteLine("Listening to TCP.");

            aDispatcher.AttachDuplexInputChannel(aPipeInputChannel);
            Console.WriteLine("Listening to Named Pipe.");

            Console.WriteLine("To stop the service press enter ...");
            Console.ReadLine();

            // Stop listening and detach all input channels.
            aDispatcher.DetachDuplexInputChannel();
        }


        // The handler called when a message is received.
        static void OnMessageReceived(object sender, TypedRequestReceivedEventArgs<RequestData> e)
        {
            if (e.ReceivingError == null)
            {
                int aResult = e.RequestMessage.Number1 + e.RequestMessage.Number2;
                Console.WriteLine("{0} + {1} = {2}", e.RequestMessage.Number1, e.RequestMessage.Number2, aResult);

                // Send the result back to the client.
                myReceiver.SendResponseMessage(e.ResponseReceiverId, aResult);
            }
        }
    }
}


Client Application
The client is a simple windows form application using the service to calculate two numbers provided by the user. The client can communicate with the service via TCP, HTTP or Named Pipe.
Since the service application is executed under administrator user rights, the named pipe client must also be executed under same rights. (HTTP and TCP clients do not have to run under administrator user rights.)

using System;
using System.Windows.Forms;
using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;

namespace TcpClient
{
    public partial class Form1 : Form
    {
        // Structure of 2 numbers that will be sent to
        // the service to be calculated.
        public class RequestData
        {
            public int Number1 { get; set; }
            public int Number2 { get; set; }
        }

        // Sender sending 'RequestData' and receiving 'int'.
        // Note: Duplex typed message sender can send messages of specified type
        //       and receive response messages of specified type.
        private static IDuplexTypedMessageSender<int, RequestData> mySender;

        public Form1()
        {
            InitializeComponent();

            // Create messaging using TCP.
            IMessagingSystemFactory aTcpMessaging = new TcpMessagingSystemFactory();
            IDuplexOutputChannel anOutputChannel =
                aTcpMessaging.CreateDuplexOutputChannel("tcp://127.0.0.1:8036/");

            //// Create messaging using HTTP.
            //IMessagingSystemFactory anHttpMessaging = new HttpMessagingSystemFactory();
            //IDuplexOutputChannel anOutputChannel = 
            //    anHttpMessaging.CreateDuplexOutputChannel("http://127.0.0.1:8035/MyService/");

            //// Create messaging using Named Pipe.
            //// Note: Do not use '/' at the end of the named pipe address.
            //IMessagingSystemFactory aNamedPipeMessaging = new NamedPipeMessagingSystemFactory();
            //IDuplexOutputChannel anOutputChannel =
            //    aNamedPipeMessaging.CreateDuplexOutputChannel("net.pipe://127.0.0.1/MyService");

            // Create the sender, that sends 'RequestData' and receives 'int'.
            IDuplexTypedMessagesFactory aTypedMessagesFactory = new DuplexTypedMessagesFactory();
            mySender = aTypedMessagesFactory.CreateDuplexTypedMessageSender<int, RequestData>();

            // Register the handler receiving the result from the service.
            mySender.ResponseReceived += OnResponseReceived;

            // Attach the TCP duplex output channel and be able to send messages
            // and receive response messages.
            mySender.AttachDuplexOutputChannel(anOutputChannel);
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            mySender.DetachDuplexOutputChannel();
        }

        private void CalculateButton_Click(object sender, EventArgs e)
        {
            // Create the message.
            RequestData aRequestData = new RequestData();
            aRequestData.Number1 = int.Parse(Number1TextBox.Text);
            aRequestData.Number2 = int.Parse(Number2TextBox.Text);

            // Send the message to the service.
            mySender.SendRequestMessage(aRequestData);
        }

        // When a result from the service is received, display it.
        private void OnResponseReceived(object sender, TypedResponseReceivedEventArgs<int> e)
        {
            if (e.ReceivingError == null)
            {
                // Display the result of the calculation.
                // Note: The response message does not come in the UI thread.
                //       Therefore, do not forget to route it if you touch UI controls.
                InvokeInUIThread(() => ResultTextBox.Text = e.ResponseMessage.ToString());
            }
        }

        // Helper executing the given delegate in the UI thread.
        private void InvokeInUIThread(Action action)
        {
            // If we are not in the UI thread then we must synchronize 
            // via the invoke mechanism.
            if (InvokeRequired)
            {
                Invoke(action);
            }
            else
            {
                action();
            }
        }
    }
}


And here are communicating applications.

No comments:

Post a Comment