Search This Blog

Wednesday, August 14, 2013

Protocol Buffers Serialization in .NET Communication

Using Protocol Buffers is much faster than BinaryFormatter. This is a short example how to use Protocol Buffers serialization for the communication between .NET applications.


Introduction

Protocol Buffers is a binary serialization originally developed by Google to share data among applications implemented in different languages like Java, C++ and Python. It became the open source and was ported to other languages and platforms too. 
The biggest advantage of Protocol Buffers is its performance and availability on multiple platforms.

The source code of the example presented in this article can be downloaded from here.

Protocol Buffers serialization is also available for .NET platforms and is much faster than classic BinaryFormatter. (Simple performance measurement.)

And Protocol Buffers are available for Eneter Messaging Framework too.

This article is a practical example showing how to use Protocol Buffers serialization for the communication between .NET applications.
The example implements a simple request-response communication where the client uses the service to calculate two numbers.

You Need to Download

  • Eneter.ProtoBuf.Serializer - protocol buffer serializer for Eneter, it also contains protocol buffer libraries and utility applications for 'proto' files.
  • Eneter.Messaging.Framework - communication framework that can be downloaded for free for non-commercial use.
If you like here are project pages for Protocol Buffers:
  • protobuf - Google implementation of Protocol Buffers for Java, C++ and Python.
  • protobuf-net - Protocol Buffers implementation from Marc Gravell for .NET platforms.
  • Eneter.ProtoBuf.Serializer - Open source project to integrate Protocol Buffers and Eneter Messaging Framework.

Add Following References into your Project

  • protobuf-net.dll - protocol buffer serializer for .NET, Windows Phone, Silverlight and Compact Framework developed by Marc Gravell.
  • Eneter.ProtoBuf.Serializer.dll - implements serializer for Eneter Messaging Framework using protobuf-net.dll.
  • Eneter.Messaging.Framework.dll - lightweight cross-platform framework for interprocess communication.

Serializing Messages Using Protocol Buffers

The original idea of Protocol Buffers is to declare messages in the 'proto' file which represents a contract describing data for the interaction.
However Protocol Buffers implementation from Marc Gravell which we use in this example allows also to declare messages without using 'proto' files. It allows to declare messages as classes which are decorated with attributes - same way .NET developers are used from DataContractSerializer.

E.g.:
[ProtoContract]
public class RequestMessage
{
    [ProtoMember(1)]
    public int Number1 { get; set; }

    [ProtoMember(2)]
    public int Number2 { get; set; }
}

Service Application

Service is a simple .NET console application listening to requests to calculate two numbers. The service uses Protocol Buffers to deserialzie incoming messages and serialize response messages.

The using of Protocol Buffers serializer is very simple. ProtoBufSerializer must be instantiated and provided to the constructor of DuplexTypedMessagesFactory. The factory will then create DuplexTypedMessageReceiver that will use ProtoBufSerializer for serializing/deserializing messages.

using System;
using Eneter.Messaging.DataProcessing.Serializing;
using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;
using Eneter.ProtoBuf;
using ProtoBuf;

namespace CalculatorService
{
    // Request message.
    [ProtoContract]
    public class RequestMessage
    {
        [ProtoMember(1)]
        public int Number1 { get; set; }

        [ProtoMember(2)]
        public int Number2 { get; set; }
    }

    // Response message.
    [ProtoContract]
    public class ResponseMessage
    {
        [ProtoMember(1)]
        public int Result { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Create ProtoBuf serializer.
            ISerializer aSerializer = new ProtoBufSerializer();

            // Create message receiver.
            IDuplexTypedMessagesFactory aReceiverFactory = new DuplexTypedMessagesFactory(aSerializer);
            IDuplexTypedMessageReceiver<ResponseMessage, RequestMessage> aReceiver =
                aReceiverFactory.CreateDuplexTypedMessageReceiver<ResponseMessage, RequestMessage>();

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

            // Use TCP for the communication.
            IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();
            IDuplexInputChannel anInputChannel =
                aMessaging.CreateDuplexInputChannel("tcp://127.0.0.1:4502/");

            // Attach the input channel to the receiver and start listening.
            aReceiver.AttachDuplexInputChannel(anInputChannel);

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

            // Detach the input channel to stop listening.
            aReceiver.DetachDuplexInputChannel();
        }

        private static void OnMessageReceived(object sender, TypedRequestReceivedEventArgs<RequestMessage> e)
        {
            // Calculate numbers.
            ResponseMessage aResponseMessage = new ResponseMessage();
            aResponseMessage.Result = e.RequestMessage.Number1 + e.RequestMessage.Number2;

            Console.WriteLine("{0} + {1} = {2}", e.RequestMessage.Number1, e.RequestMessage.Number2, aResponseMessage.Result);

            // Send back the response message.
            var aReceiver = (IDuplexTypedMessageReceiver<ResponseMessage, RequestMessage>)sender;
            aReceiver.SendResponseMessage(e.ResponseReceiverId, aResponseMessage);
        }
    }
}

Client Application

Client is a simple UI application allowing users to enter two numbers. It then uses the service application to calculate these numbers. The client uses Protocol Buffers to serialize request messages and deserialize incoming response messages.

The using of Protocol Buffers is very simple. ProtoBufSerializer must be instantiated and provided to the constructor of DuplexTypedMessagesFactory. The factory will then create DuplexTypedMessageSender that will use ProtoBufSerializer for serializing/deserializing messages.

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

namespace CalculatorClientSync
{
    public partial class Form1 : Form
    {
        // Request message.
        [ProtoContract]
        public class RequestMessage
        {
            [ProtoMember(1)]
            public int Number1 { get; set; }

            [ProtoMember(2)]
            public int Number2 { get; set; }
        }

        // Response message.
        [ProtoContract]
        public class ResponseMessage
        {
            [ProtoMember(1)]
            public int Result { get; set; }
        }

        private ISyncDuplexTypedMessageSender<ResponseMessage, RequestMessage> mySender;

        public Form1()
        {
            InitializeComponent();

            OpenConnection();
        }

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

        private void OpenConnection()
        {
            // Create Protocol Buffers serializer.
            ISerializer aSerializer = new ProtoBufSerializer();

            // Create the synchronous message sender.
            // It will wait max 5 seconds for the response.
            // To wait infinite time use TimeSpan.FromMiliseconds(-1) or
            // default constructor new DuplexTypedMessagesFactory()
            IDuplexTypedMessagesFactory aSenderFactory =
                new DuplexTypedMessagesFactory(TimeSpan.FromSeconds(5), aSerializer);
            mySender = aSenderFactory.CreateSyncDuplexTypedMessageSender<ResponseMessage, RequestMessage>();

            // Use TCP for the communication.
            IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();
            IDuplexOutputChannel anOutputChannel =
                aMessaging.CreateDuplexOutputChannel("tcp://127.0.0.1:4502/");

            // Attach the output channel and be able to send messages
            // and receive response messages.
            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.
            // It waits until the response is received.
            ResponseMessage aResponse = mySender.SendRequestMessage(aRequest);

            // Display the result.
            ResultTextBox.Text = aResponse.Result.ToString();
        }
    }
}

No comments:

Post a Comment