Search This Blog

Sunday, May 11, 2014

HTML5: Request-Response Communication Using WebSockets

Simple example showing request-'multiple response' communication between HTML5 JavaScript and .NET using the WebSocket protocol.


The source code for this example can be downloaded here.

Introduction

This is a free continuation of HTML5: Real-Time Push Notifications from .NET Application that shows how to push notification messages instantly without using polling or long-polling mechanism.

The example bellow demonstrates another type of scenario. It shows request-'multiple response' communication where JavaScript sends a request message and .NET application sends back multiple response messages.

The communication is realized via WebSockets (full-duplex single socket connection) and therefore it is not needed to use polling or long-polling mechanism to obtain multiple responses (as would be used in case of HTTP protocol).
Using WebSockets allows to open connection and keep it open as long as needed. While the connection is open the client can send multiple request messages and the service can send multiple response messages whenever needed.

The example is based on Eneter Messaging Framework the free lightweight cross-platform framework for the interprocess communication:

Example Application

In order to demonstrate the request-'multiple response' scenario between JavaScript and .NET the example bellow implements a simple HTML 5 web-page and a simple .NET console application.
The HTML 5 web-page is a client which opens the WebSocket connection with .NET and requests to calculate the PI number.
The .NET console application acts as a service. When it receives a request it starts to calculate the PI number and after each calculation step it responses to the web-page the current result.

To Run Example

  • Download and unzip this example.
  • Download Eneter for .NET from http://www.eneter.net/ProductDownload.htm.
  • Open the example project in Visual Studio and add the reference to Eneter.Messaging.Framework.dll that you downloaded.
  • Build the application and execute it.
  • Open index.html (from HTML5PiClient directory) in an internet browser.
  • Press 'Open Connection' button and then 'Calculate PI' button.
  • You will see the web-page will get several responses and finally it will display the calculated PI number.

.NET Service Application

The service application is a simple .NET console application listening via WebSockets to clients. When a client connects and sends the request it calculate the PI number in the loop and sends back responses containing the current state of the calculation.
The service uses JSON serialization so that the web client application can deserialize it.

The whole implementation is very simple:
using System;
using Eneter.Messaging.DataProcessing.Serializing;
using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.WebSocketMessagingSystem;

namespace PiService
{
    public class PiRequestMessage
    {
        public double CalculationStep { get; set; }
    }

    public class PiResponseMessage
    {
        public double Result { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Use JSON serializer which can be used by JavaScript too.
            ISerializer aJsonSerialzier = new DataContractJsonStringSerializer();

            // Factory to create message receivers.
            IDuplexTypedMessagesFactory aReceiverFactory = new DuplexTypedMessagesFactory(aJsonSerialzier);

            // Create message receiver.
            IDuplexTypedMessageReceiver<PiResponseMessage, PiRequestMessage> aReceiver
                = aReceiverFactory.CreateDuplexTypedMessageReceiver<PiResponseMessage, PiRequestMessage>();

            // Subscribe to process incoming messages.
            aReceiver.MessageReceived += OnMessageReceived;

            // Subscribe to observe client connections/disconnections.
            aReceiver.ResponseReceiverConnected += OnClientConnected;
            aReceiver.ResponseReceiverDisconnected += OnClientDisconnected;

            // Use WebSocket for the communication.
            IMessagingSystemFactory aMessaging = new WebSocketMessagingSystemFactory();

            // Attach input channel and be able to receive request messages
            // and send back response messages.
            IDuplexInputChannel anInputChennel = aMessaging.CreateDuplexInputChannel("ws://127.0.0.1:8091/PiCalculator/");
            aReceiver.AttachDuplexInputChannel(anInputChennel);

            Console.WriteLine("Pi service is running. Press ENTER to stop.");
            Console.ReadLine();

            // Detach input channel and stop listening.
            // Note: it releases the listening thread.
            aReceiver.DetachDuplexInputChannel();
        }

        
        // Method which is invoked when the request message is received.
        static void OnMessageReceived(object sender, TypedRequestReceivedEventArgs<PiRequestMessage> e)
        {
            // Get the receiver instance.
            IDuplexTypedMessageReceiver<PiResponseMessage, PiRequestMessage> aReceiver
                = (IDuplexTypedMessageReceiver<PiResponseMessage, PiRequestMessage>)sender;

            PiResponseMessage aResponseMessage = new PiResponseMessage();

            // Calculate PI and send back multiple response messages.
            double aResult = 0.0;
            double aDx = e.RequestMessage.CalculationStep;
            for (double x = -1.0; x < 1.0; x += aDx)
            {
                aResult += 2 * Math.Sqrt(1 - x * x) * aDx;

                // Send back the current result.
                // Note: ResponseReceiverId identifies the client who sent the request.
                aResponseMessage.Result = aResult;
                aReceiver.SendResponseMessage(e.ResponseReceiverId, aResponseMessage);
            }
        }

        private static void OnClientConnected(object sender, ResponseReceiverEventArgs e)
        {
            Console.WriteLine("Client connected: " + e.ResponseReceiverId);
        }

        private static void OnClientDisconnected(object sender, ResponseReceiverEventArgs e)
        {
            Console.WriteLine("Client disconnected: " + e.ResponseReceiverId);
        }
        
    }
}

JavaScript Client

The JavaScript client is a simple HTML 5 web-page. It uses Eneter for JavaScript to send request messages and receive responses. It allows to open the connection with the service and send the request message to calculate the PI number. Then it receives multiple response messages as the service proceeds with the calculation.

The implementation of the web-page is very simple:

<!DOCTYPE html>
<html>
    <head>
        <title>PI Calculator Client</title>
        
        <!-- Import Eneter for JavaScript -->
        <script src="eneter-messaging-6.0.1.js"></script>
        
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body onunload="closeConnection();">
        <div>
            <input type="button" onclick="openConnection();" value="Open Connection" /><br/>
            <br/>
            Calculation Step: <input id="CalculationStep" type="number" value="0.001" />
            <input type="submit" onclick="calculatePi();" value="Calculate PI" /><br/>
            Result: <input type="text" id="Result" value="" /><br/>
            <br/>
            <input type="button" onclick="closeConnection();" value="Close Connection" />
        </div>
        
        <script>
            // Create message senders and provide callback to
            // process response messages.
            var myPiSender = new DuplexTypedMessageSender();
            myPiSender.onResponseReceived = onResultReceived;
            
            // Request message structure.
            // Note: This structure will be serialized and
            //       sent as the message.
            function PiRequestMessage(calculationStep) {
                this.CalculationStep = calculationStep;
            }
            
            function openConnection() {
                // Attach output channels and be able to send messages
                // and receive response messages.
                var anOutputChannel =
                     new WebSocketDuplexOutputChannel("ws://127.0.0.1:8091/PiCalculator/", null);
                myPiSender.attachDuplexOutputChannel(anOutputChannel);
            };
            
            function closeConnection() {
                // Detach output channels and close connections.
                myPiSender.detachDuplexOutputChannel();
            };
            
            function calculatePi() {
                // Create request message.
                var aCalculationStep = document.getElementById("CalculationStep").value;
                var aRequestMessage = new PiRequestMessage(aCalculationStep);
                
                // Send request to sum numbers.
                // Note: the message will be serialized by JSON
                //       and sent to the service.
                // Note: service will send multiple response messages
                //       per one request message.
                myPiSender.sendRequestMessage(aRequestMessage);
            };
            
            // Callback method called when a response message is received.
            // Note: one request method will cause multiple responses.
            function onResultReceived(typedResponseReceivedEventArgs) {
                var aNumber = typedResponseReceivedEventArgs.ResponseMessage.Result;
                
                // Display the result.
                var anElement = document.getElementById("Result");
                anElement.value = aNumber;
            };
        </script>
    </body>
</html>

12 comments:

  1. How do I get it to work over android?

    ReplyDelete
    Replies
    1. Please check following examples for Android:
      http://www.codeproject.com/Articles/340714/Android-How-to-communicate-with-NET-application-vi

      If it is not sufficient please e-mail me your scenario I will try to support you.

      Delete
  2. Can I send string to PC .NET application from web browser or microcontroller? I want to send something like . It is not possible to implement ENETER on ATmega, but sending bytes via Wi-Fi is available.

    ReplyDelete
  3. ... something like http:192.168.1.104/my_command from web browser or "my_command" to ip address from uC ...

    ReplyDelete
    Replies
    1. If I understand correctly, you have a device where you can use Eneter but you would like to communicate with an Eneter .NET application on a PC.
      If my understanding is correct then yes, you can communicate, but you need to send your bytes encoded in the Eneter protocol.
      Or you can provide your own protocol (I mean you can provide your own encoding of bytes.) if you implement IProtocolFormatter.
      Then you can pass your instance of IProtocolFormatter to the constructor when you creating a particular messaging.
      E.g.:
      IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory(yourProtocolFormatter);

      Delete
    2. Thanks for help. I will try a second solution, Eneter documentation is very good. Great work, greetings!

      Delete
  4. This example has the Javascript client sending requests to the .NET service.

    Is there an example where the .NET service sends requests to a Javascript client, and waits for a response?

    ReplyDelete
    Replies
    1. Right now I do not have such example. But maybe I can give you some hints how to do it.
      When the Javascript client connects the .NET service the event ResponseReceiverConnected is raised. The event contains ResponseReceiverId property which identifies the connected client during whole connection.
      Then anytime a service needs to send a message to the client it can just call:
      aResponseReceiver.SendResponseMessage(responseReceiverId, message);

      (.. so SendResponseMessage(..) does not have to be called only as a response for a particular request but anytime during the connection)

      Delete
  5. "Firefox could not connect to ws://192.168.1.13:8091/test/"

    vb.NET
    Dim aReceiverFactory As IDuplexTypedMessagesFactory = New DuplexTypedMessagesFactory()

    Dim server As IDuplexTypedMessageReceiver(Of cls_tcp_response, cls_tcp_request)
    server = aReceiverFactory.CreateDuplexTypedMessageReceiver(Of cls_tcp_response, cls_tcp_request)()

    AddHandler server.MessageReceived, AddressOf OnMessageReceived
    AddHandler server.ResponseReceiverConnected, AddressOf OnResponseReceiverConnected
    AddHandler server.ResponseReceiverDisconnected, AddressOf OnResponseReceiverDisconnected

    Dim aMessaging As New TcpMessagingSystemFactory()
    Dim anInputChannel As IDuplexInputChannel = aMessaging.CreateDuplexInputChannel("ws://0.0.0.0:8091/test/")

    server.AttachDuplexInputChannel(anInputChannel)

    Using the same over TCP with android works :/

    ReplyDelete
    Replies
    1. The typical problem in communication with an HTML5 client is that the server does not use the JSON serializer. (If the serializer is not setup the XmlSerialiser is used by default. But HTML5 javascript client uses JSON serializer.)
      Therefore please ensure your .NET service setups the JSON serializer:

      // Use JSON serializer which can be used by JavaScript too.
      ISerializer aJsonSerialzier = new DataContractJsonStringSerializer();

      // Factory to create message receivers.
      IDuplexTypedMessagesFactory aReceiverFactory = new DuplexTypedMessagesFactory(aJsonSerialzier);

      Delete
  6. Thank's for your article..
    if i have a mobile application build in java script and html.. how i can communicate between this application and anther application build with vb.net on pc server..

    ReplyDelete