Search This Blog

Wednesday, July 4, 2012

Eneter Messaging Framework 4.1

Summary: Eneter supports communication via Websockets.


Introduction

As you probably know, Eneter Messaging Framework is a cross-platform framework for interprocess communication.

The purpose of this project is to create a lightweight library that
  • will be easy to use.
  • will work the same way across various platforms.
  • will provide functionality to deal with specifics of interprocess communication instead of abstracting it into a local call.
  • will give you well defined components you can combine to implement various communication scenarios.
I believe the framework based on these principles will keep the communication clean and simple.

Technical summary describing capabilities of this framework can be found here:
http://www.eneter.net/ProductInfo.htm

Eneter Messaging Framework 4.1 is dedicated to Websockets.
The Websocket is a communication protocol standardized by IETF as RFC 6455.

The main advantage of this protocol is that the communication starts as an ordinary HTTP request but then the connection stays open and provides bi-directional and true full-duplex communication.
Therefore, it is not needed to use overheads as polling or long-polling (like in HTTP) to emulate the duplex communication (specifically transfering messages from server to client).

Eneter 4.1 incorporates this protocol and enables you to use it on following platforms:
  • .NET3.5, .NET4.0, Java 6, Android 2.1 (or later), Silverlight 3, Silverlight 4, Silverlight 5 and Windows Phone 7.1
The functionality can be found under the namespace
Eneter.Messaging.MessagingSystems.WebSocketMessagingSystem.

Using eneter library you can choose to communicate directly on the websocket level or you can use websockets with eneter components to implement various communication scenarios.

(Examples bellow are implemented in C# but the very same functionality is available in Java and Android too!)

Websocket Level

Using functionality on the websocket level will enable you to communicate with any standard websocket client or server. E.g. you can implement a websocket server communicating with a java script HTML 5 application.
To communicate on the websocket level you can use:
  • WebSocketClient - is a websocket client that can send text and binary messages to a websocket server and receive text and binary response messages.
  • WebSocketListener - is a websocket server listening to incoming connections from websocket clients.

The example can be downloaded here:
http://www.eneter.net/Downloads/Examples/WebSocketEcho.zip

The following code shows the client communicating with the echo server.


using System;
using System.Windows.Forms;
using Eneter.Messaging.MessagingSystems.WebSocketMessagingSystem;

namespace EchoClient
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void SendBtn_Click(object sender, EventArgs e)
        {
            // Create the websocket client.
            // Note: The port number must be provided!
            WebSocketClient aClient =
                new WebSocketClient(new Uri("ws://echo.websocket.org:80/"));

            // Subscribe to receive response messages.
            aClient.MessageReceived += OnMessageReceived;

            // Open the connection.
            aClient.OpenConnection();

            // Send the message.
            aClient.SendMessage(MessageTextBox.Text);
        }

        // The method is called when the echo response is received.
        private void OnMessageReceived(object sender, WebSocketMessage e)
        {
            // Get the message.
            string aResponseMessage = e.GetWholeTextMessage();

            // The response is not received in the UI thread.
            // So when we want to update a UI control we must do it in the UI  thread.
            UI(() => ResponseMessageLabel.Text = aResponseMessage);

            // Close the connection with the websocket server.
            ((WebSocketClient)(sender)).CloseConnection();
        }

        private void UI(Action action)
        {
            if (InvokeRequired)
            {
                Invoke(action);
            }
            else
            {
                action();
            }
        }
    }
}

The following code shows how to implement a simple websocket service (echo server).

using System;
using Eneter.Messaging.MessagingSystems.WebSocketMessagingSystem;

namespace EchoService
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create the service.
            WebSocketListener aService
                = new WebSocketListener(new Uri("ws://127.0.0.1:8045/Echo/"));

            // Provide the callback handling incoming connection and start listening.
            // The callback will be called from multiple threads as connections
            // are accepted.
            aService.StartListening(client =>
                {
                    // Loop for incominf messages for this client.
                    WebSocketMessage aMessage;
                    while ((aMessage = client.ReceiveMessage()) != null)
                    {
                        object aData;
                        if (aMessage.IsText)
                        {
                            // if text message is received.
                            aData = aMessage.GetWholeTextMessage();
                        }
                        else
                        {
                            // if binary message is received.
                            aData = aMessage.GetWholeMessage();
                        }

                        // Send back the echo - same message.
                        client.SendMessage(aData);
                    }
                });

            Console.WriteLine("WebSocket service is listening. Press Enter to stop.");
            Console.ReadLine();

            aService.StopListening();
        }
    }
}

Websockets with Eneter Components

Eneter components provide you a sophisticated functionality to implement various communication scenarios.

To use websockets with eneter components you may want to use WebSocketMessagingSystemFactory that creates instances of channels (IDuplexOutputChannel, IDuplexInputChannel, ...) that can be then attached to eneter components - it works the same way as channels of any other protocol e.g. TCP or HTTP.

The example can be downloaded here:
http://www.eneter.net/Downloads/Examples/WebsocketCalculator.zip


The following code shows how to implement a simple server providing various calculation services.

using System;
using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.WebSocketMessagingSystem;

namespace ServerCalculator2
{
    // Input data for calculator requests
    public class CalculatorInputData
    {
        public double Number1 { get; set; }
        public double Number2 { get; set; }
    }

    // Output result from the calculator
    public class CalculatorOutputData
    {
        public double Result { get; set; }
    }

    internal class Calculator
    {
        // Receivers receiving CalculatorInputData and responding CalculatorOutputData
        private IDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData> mySumReceiver;
        private IDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData> mySubtractReceiver;
        private IDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData> myMultiplyReceiver;
        private IDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData> myDivideReceiver;


        public Calculator()
        {
            // Factory to create message receivers - services.
            IDuplexTypedMessagesFactory aMessageReceiverFactory = new DuplexTypedMessagesFactory();
            
            // Create receiver to sum two numbers.
            mySumReceiver = aMessageReceiverFactory.CreateDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData>();
            mySumReceiver.MessageReceived += SumCmd; // attach method handling the request

            // Receiver to subtract two numbers.
            mySubtractReceiver = aMessageReceiverFactory.CreateDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData>();
            mySubtractReceiver.MessageReceived += SubCmd; // attach method handling the request

            // Receiver for multiply two numbers.
            myMultiplyReceiver = aMessageReceiverFactory.CreateDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData>();
            myMultiplyReceiver.MessageReceived += MulCmd; // attach method handling the request

            // Receiver for divide two numbers.
            myDivideReceiver = aMessageReceiverFactory.CreateDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData>();
            myDivideReceiver.MessageReceived += DivCmd; // attach method handling the request
        }


        public void StartCalculatorService()
        {
            // Use WebSockets for the communication.
            IMessagingSystemFactory aMessaging = new WebSocketMessagingSystemFactory();

            // Attach duplex input channels and start listening.
            // Note: It creates websocket services listening on different paths but
            //       on the same IP address and port.
            mySumReceiver.AttachDuplexInputChannel(
                aMessaging.CreateDuplexInputChannel("ws://127.0.0.1:8091/Sum/"));
            mySubtractReceiver.AttachDuplexInputChannel(
                aMessaging.CreateDuplexInputChannel("ws://127.0.0.1:8091/Sub/"));
            myMultiplyReceiver.AttachDuplexInputChannel(
                aMessaging.CreateDuplexInputChannel("ws://127.0.0.1:8091/Mul/"));
            myDivideReceiver.AttachDuplexInputChannel(
                aMessaging.CreateDuplexInputChannel("ws://127.0.0.1:8091/Div/"));
        }

        public void StopCalculatorService()
        {
            // Stop listening to all services.
            mySumReceiver.DetachDuplexInputChannel();
            mySubtractReceiver.DetachDuplexInputChannel();
            myMultiplyReceiver.DetachDuplexInputChannel();
            myDivideReceiver.DetachDuplexInputChannel();
        }

        // It is called when a request to sum two numbers was received.
        private void SumCmd(object sender, TypedRequestReceivedEventArgs<CalculatorInputData> e)
        {
            // Get input data.
            CalculatorInputData anInputData = e.RequestMessage;

            // Calculate output result.
            CalculatorOutputData aReturn = new CalculatorOutputData();
            aReturn.Result = anInputData.Number1 + anInputData.Number2;

            Console.WriteLine("{0} + {1} = {2}",
                anInputData.Number1, anInputData.Number2, aReturn.Result);

            // Response result to the client.
            mySumReceiver.SendResponseMessage(e.ResponseReceiverId, aReturn);
        }

        // It is called when a request to subtract two numbers was received.
        private void SubCmd(object sender, TypedRequestReceivedEventArgs<CalculatorInputData> e)
        {
            // Get input data.
            CalculatorInputData anInputData = e.RequestMessage;

            // Calculate output result.
            CalculatorOutputData aReturn = new CalculatorOutputData();
            aReturn.Result = anInputData.Number1 - anInputData.Number2;

            Console.WriteLine("{0} - {1} = {2}",
                anInputData.Number1, anInputData.Number2, aReturn.Result);

            // Response result to the client.
            mySubtractReceiver.SendResponseMessage(e.ResponseReceiverId, aReturn);
        }
        

        // It is called when a request to multiply two numbers was received.
        private void MulCmd(object sender, TypedRequestReceivedEventArgs<CalculatorInputData> e)
        {
            // Get input data.
            CalculatorInputData anInputData = e.RequestMessage;

            // Calculate output result.
            CalculatorOutputData aReturn = new CalculatorOutputData();
            aReturn.Result = anInputData.Number1 * anInputData.Number2;

            Console.WriteLine("{0} x {1} = {2}",
                anInputData.Number1, anInputData.Number2, aReturn.Result);

            // Response result to the client.
            myMultiplyReceiver.SendResponseMessage(e.ResponseReceiverId, aReturn);
        }

        // It is called when a request to divide two numbers was received.
        private void DivCmd(object sender, TypedRequestReceivedEventArgs<CalculatorInputData> e)
        {
            // Get input data.
            CalculatorInputData anInputData = e.RequestMessage;

            // Calculate output result.
            CalculatorOutputData aReturn = new CalculatorOutputData();
            aReturn.Result = anInputData.Number1 / anInputData.Number2;

            Console.WriteLine("{0} / {1} = {2}",
                anInputData.Number1, anInputData.Number2, aReturn.Result);

            // Response result to the client.
            myDivideReceiver.SendResponseMessage(e.ResponseReceiverId, aReturn);
        }
    }
}

The following code shows how to implement a client using the server to calculate numbers.

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

namespace CalculatorClient2
{
    public partial class Form1 : Form
    {
        // Input data for calculator requests
        public class CalculatorInputData
        {
            public double Number1 { get; set; }
            public double Number2 { get; set; }
        }

        // Output result from the calculator
        public class CalculatorOutputData
        {
            public double Result { get; set; }
        }

        // Message senders sending CalculatorInputData and receiving CalculatorOutputData.
        private IDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData> mySumSender;
        private IDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData> mySubSender;
        private IDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData> myMulSender;
        private IDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData> myDivSender;

        public Form1()
        {
            InitializeComponent();

            // Use messaging based on websockets.
            IMessagingSystemFactory aMessaging = new WebSocketMessagingSystemFactory();

            // Factory to create message senders.
            IDuplexTypedMessagesFactory aCommandsFactory = new DuplexTypedMessagesFactory();

            // Sender to sum two numbers.
            // Note: Attach websocket channel and be able to send messages and receive responses.
            mySumSender = aCommandsFactory.CreateDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData>();
            mySumSender.ResponseReceived += OnResultResponse;
            mySumSender.AttachDuplexOutputChannel(
                aMessaging.CreateDuplexOutputChannel("ws://127.0.0.1:8091/Sum/"));

            // Sender to subtract two numbers.
            mySubSender = aCommandsFactory.CreateDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData>();
            mySubSender.ResponseReceived += OnResultResponse;
            mySubSender.AttachDuplexOutputChannel(
                aMessaging.CreateDuplexOutputChannel("ws://127.0.0.1:8091/Sub/"));

            // Sender to multiply two numbers.
            myMulSender = aCommandsFactory.CreateDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData>();
            myMulSender.ResponseReceived += OnResultResponse;
            myMulSender.AttachDuplexOutputChannel(
                aMessaging.CreateDuplexOutputChannel("ws://127.0.0.1:8091/Mul/"));

            // Sender to divide two numbers.
            myDivSender = aCommandsFactory.CreateDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData>();
            myDivSender.ResponseReceived += OnResultResponse;
            myDivSender.AttachDuplexOutputChannel(
                aMessaging.CreateDuplexOutputChannel("ws://127.0.0.1:8091/Div/"));
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            // Stop listening by detaching the input channels.
            mySumSender.DetachDuplexOutputChannel();
            mySubSender.DetachDuplexOutputChannel();
            myMulSender.DetachDuplexOutputChannel();
            myDivSender.DetachDuplexOutputChannel();
        }


        private void OnResultResponse(object sender, TypedResponseReceivedEventArgs<CalculatorOutputData> e)
        {
            // If everything is ok then display the result.
            if (e.ReceivingError == null)
            {
                // The response does not come in main UI thread.
                // Therefore we must transfer it to the main UI thread.
                UI(() => ResultLabel.Text = e.ResponseMessage.Result.ToString() );
            }
        }

        private void CalculateButton_Click(object sender, EventArgs e)
        {
            // Prepare input data for the calculator.
            CalculatorInputData anInputForCalculator = new CalculatorInputData();
            anInputForCalculator.Number1 = double.Parse(Number1TextBox.Text);
            anInputForCalculator.Number2 = double.Parse(Number2TextBox.Text);

            // Invoke request to sum.
            mySumSender.SendRequestMessage(anInputForCalculator);
        }
         
        private void SubtractButton_Click(object sender, EventArgs e)
        {
            // Prepare input data for the calculator.
            CalculatorInputData anInputForCalculator = new CalculatorInputData();
            anInputForCalculator.Number1 = double.Parse(Number1TextBox.Text);
            anInputForCalculator.Number2 = double.Parse(Number2TextBox.Text);

            // Invoke request to substract.
            mySubSender.SendRequestMessage(anInputForCalculator);
        }

        private void MultiplyButton_Click(object sender, EventArgs e)
        {
            // Prepare input data for the calculator.
            CalculatorInputData anInputForCalculator = new CalculatorInputData();
            anInputForCalculator.Number1 = double.Parse(Number1TextBox.Text);
            anInputForCalculator.Number2 = double.Parse(Number2TextBox.Text);

            // Invoke request to multiply.
            myMulSender.SendRequestMessage(anInputForCalculator);
        }

        private void DivideButton_Click(object sender, EventArgs e)
        {
            // Prepare input data for the calculator.
            CalculatorInputData anInputForCalculator = new CalculatorInputData();
            anInputForCalculator.Number1 = double.Parse(Number1TextBox.Text);
            anInputForCalculator.Number2 = double.Parse(Number2TextBox.Text);

            // Invoke request to divide.
            myDivSender.SendRequestMessage(anInputForCalculator);
        }

        // Helper method to invoke UI always in the correct thread.
        private void UI(Action action)
        {
            if (InvokeRequired)
            {
                Invoke(action);
            }
            else
            {
                action.Invoke();
            }
        }
    }
}

1 comment:

  1. Hi ondrej,

    I'm exploring your framework and this looks great for a very simple task that I've to work on. But the first test to simple websocket echo call using this method is giving me "A required header field was missing." error on OpenConnection.

    WebSocketClient aClient = new WebSocketClient(new Uri("ws://echo.websocket.org:80/"));

    I believe I'm missing something very obvious?

    Shadab

    ReplyDelete