Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Invisible Interprocess Communication

0.00/5 (No votes)
23 Aug 2016 1  
Use dependency injection to communicate seamlessly with remote services.

Introduction

Implementing client/server apps for years it has always been a pain communicating with other remote components. Always serializing data in a certain protocol message and de-serializing it. The apps were dependent of the transfer technology and formats. How great would it be if all the technical bonding could be removed and only the functional contract matters?
Dependency injection removed the technical dependencies within an application years ago. My idea was to extend this concept beyond application boundaries. The result is an open source library called IOC-Talk.

I know that WCF offers some of the same promises. But the configuration is a mess and you are strongly bound to WCF itself. Another reason is that it’s hard to implement a session orientated two way communication scenario.

Plumbing

The aim was to design independent functional interfaces and use/implement them in no other way you would in a standard dependency injection scenario. But this is not enough. We need a tiny layer above to manage different connections. IOC-Talk creates one instance of the contract services for each session. All service interface imports are encapsulated in a custom contract session class (defining the supported services for both comm. directions).

Open lightweight library

The use of such a library is only recommendable if you don’t end up in another dead end (dependency). That’s why IOC-Talk is designed to support the seamlessly replacement of the transfer technology and message serialization layers without changing your functional business code. The only point of contact is the chosen dependency container and if you need functional session management.

The currently available IOC-Talk library implements the following concrete software stack:

  • MEF Dependency Container
  • JSON Message Serialization
  • TCP/IP Socket Communication

How it works

The following sequence describes the session establishment:

  1. Initializing the communication infrastructure and assigning the local session contract class (containing the desired local and remote interface imports).
  2. If the connection is established, a new instance of the custom contract class is created.
  3. The dependency container is asked to satisfy all imports of the created contract session instance.
  4. If the dependency container can’t find a local implementation of the service, a proxy implementation to call the remote service is automatically implemented (custom proxy implementations are supported as well).
  5. All instances created within this process are assigned to the connection session.
  6. Invoke session created events/interface methods

Now using the session contract instance the application can call any interface service on the remote side and is able to process incoming invoke requests of the local service implementations.

The following sequence describes the call of a remote service:

  1. Functional app code calls an interface method using the instance from the session contract.
  2. This will trigger the method of the proxy implementation.
  3. The proxy implementation will redirect the call and the given parameter to the communication host.
  4. Then the call is serialized as a message using the serialization service (JSON) and sent to the remote endpoint (TCP).
  5. At the remote side the message is de-serialized.
  6. The dependency container is asked for an instance of the target interface.
  7. The method is called and the return value is sent back to the origin.

Using the code

To demonstrate how the library can be used in practice I ‘am going to set up a simple client/server application step by step. The app will record the cpu utilization over several nodes. Therefore we are going to implement a client console application that measures periodically the cpu utilization and publishes the results to a central server app.
The server console application will log the results.

  1. First we create a blank solution in Visual Studio called “PerformanceRecorder”.
  2. We add the C# class library “PerformanceRecorder.Interface” to define the interface.
  3. We need a functional service interface for our central service:
public interface IPerformanceObserverService
{
    void PublishProcessorUsage(IProcessorUsage pUsage);
}
  1. And an interface for the data transfer object:
public interface IProcessorUsage
{
    /// <summary>
    /// Gets or sets the measure time
    /// </summary>
    DateTime Time { get; set; }

    /// <summary>
    /// Gets or sets the cpu usage in percent
    /// </summary>
    int UsagePercent { get; set; }
  
    /// <summary>
    /// Gets or sets the source machine name
    /// </summary>
    string Source { get; set; }
}
  1. Now our service contract is defined and we can start implementing the service.
  2. Add a new class library called “PerformanceRecorder.Observer” and add a reference to “PerformanceRecorder.Interface”.
  3. Create a new class “PerformanceObserverService” derived from “IPerformanceObserverService”
public class PerformanceObserverService : IPerformanceObserverService
{
    public void PublishProcessorUsage(IProcessorUsage pUsage)
    {
        Console.WriteLine("{0} - {1} % CPU usage on \"{2}\"", pUsage.Time.ToLongTimeString(), pUsage.UsagePercent, pUsage.Source);

        //todo: serialize and write to local file
    }
}
  1. To be discoverable by dependency injection we add a reference to .NET MEF library (System.ComponentModel.Composition) and export our service interface.
using System.ComponentModel.Composition;

// ...

    [Export(typeof(IPerformanceObserverService))]

    public class PerformanceObserverService : IPerformanceObserverService
  1. To run our implemented service we create a new console application “PerformanceRecorder. ServiceConsole” and add references to “.Interface” and “.Observer”.
  2. Every physical connection is represented by a contract session class that imports all for the communication required service interfaces. This includes local and remote implemented service interfaces. In the example we just support one server-side implemented service:
using System.ComponentModel.Composition;
using PerformanceRecorder.Interface;

namespace PerformanceRecorder.ServiceConsole
{
    public class PerfRecordServiceContract
    {
        [Import]
        public IPerformanceObserverService ObserverService { get; set; }
    }
}
  1. Now it’s time to add IOC-Talk references to the ServiceConsole project.
    We install the NuGet package “ioctalk”.
     
  2. To complete the server side we link all together using the App.config within the server console project:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="IOCTalk" type="BSAG.IOCTalk.Communication.Common.Factory.AppConfigSection, BSAG.IOCTalk.Communication.Common"/>
  </configSections>

  <IOCTalk>
    <SessionContract type="PerformanceRecorder.ServiceConsole.PerfRecordServiceContract, PerformanceRecorder.ServiceConsole" name="Sample Service">
      <Communication type="BSAG.IOCTalk.Communication.Tcp.TcpCommunicationController, BSAG.IOCTalk.Communication.Tcp">
        <ConnectionType>Service</ConnectionType>
        <Port>50123</Port>
        <Security enabled="false">
          <CertificateName>YourCertificateName</CertificateName>
          <ClientCertificateRequired>false</ClientCertificateRequired>
        </Security>
      </Communication>
      <Container type="BSAG.IOCTalk.Container.MEF.TalkContainerHostMEF`1, BSAG.IOCTalk.Container.MEF" />
    </SessionContract>
  </IOCTalk>

  <system.diagnostics>
    <trace autoflush="false" indentsize="4">
      <listeners>
        <add name="configConsoleListener"
          type="System.Diagnostics.ConsoleTraceListener" traceOutputOptions="DateTime" />
      </listeners>
    </trace>

  </system.diagnostics>

    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>
</configuration>

 

  1. In the Program.cs Main method we initialize the communication infrastructure:
using BSAG.IOCTalk.Communication.Common.Factory;
using System;

namespace PerformanceRecorder.ServiceConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("-------------------------------------------");
            Console.WriteLine("Performance Recorder Server");
            Console.WriteLine("-------------------------------------------");
            Console.WriteLine();

            var commServices = IOCTalkLoader.InitFromAppConfig();

            Console.WriteLine();
            Console.WriteLine("Press [Enter] to exit");
            Console.ReadLine();

            foreach (var commService in commServices)
            {
                commService.Shutdown();
            }
        }
    }
}
  1. The server is now ready for client sessions on port 30123:

<img alt="" src="1095181/Console1.png" />

  1. The client implementation requires similar plumbing without the service implementation.
  2. New console application: “PerformanceRecorder.ClientConsole”
  3. References: “PerformanceRecorder.Interface”, MEF and IOC-Talk (Nuget package)
  4. Implement a client session contract and import the remote service interface:
public class PerfRecordClientContract
{
    [Import]
    public IPerformanceObserverService ObserverService { get; set; }
}
  1. Extend client console App.config like:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="IOCTalk" type="BSAG.IOCTalk.Communication.Common.Factory.AppConfigSection, BSAG.IOCTalk.Communication.Common"/>
  </configSections>

  <IOCTalk>
    <SessionContract type="PerformanceRecorder.ClientConsole.PerfRecordClientContract, PerformanceRecorder.ClientConsole" name="Sample Client">
      <Communication type="BSAG.IOCTalk.Communication.Tcp.TcpCommunicationController, BSAG.IOCTalk.Communication.Tcp">
        <ConnectionType>Client</ConnectionType>
        <Host>127.0.0.1</Host>
        <Port>30123</Port>
        <Security enabled="false">
          <ServerName>YourCertificateName</ServerName>
          <ProvideClientCertificate>false</ProvideClientCertificate>
          <ClientCertificateName>YourCertificateName</ClientCertificateName>
        </Security>
      </Communication>
      <Container type="BSAG.IOCTalk.Container.MEF.TalkContainerHostMEF`1, BSAG.IOCTalk.Container.MEF">
        <CreateDebugEnabledAssembly>true</CreateDebugEnabledAssembly>
      </Container>
    </SessionContract>
  </IOCTalk>

  <system.diagnostics>
    <trace autoflush="false" indentsize="4">
      <listeners>
        <add name="configConsoleListener"
          type="System.Diagnostics.ConsoleTraceListener" traceOutputOptions="DateTime" />
      </listeners>
    </trace>
  </system.diagnostics>

  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
  </startup>
</configuration>
  1. Initialize communication infrastructure within the client console app:
static void Main(string[] args)
{
    Console.WriteLine("-------------------------------------------");
    Console.WriteLine("Performance Recorder Client");
    Console.WriteLine("-------------------------------------------");
    Console.WriteLine();

    var commServices = IOCTalkLoader.InitFromAppConfig();

    Console.WriteLine();
    Console.WriteLine("Press [Enter] to exit");
    Console.ReadLine();

    foreach (var commService in commServices)
    {
        commService.Shutdown();
    }
}
  1. We have almost reached our goal. The only thing left is to periodically push the cpu usage if a connection is established on client side. For this we implement the “ISessionStateChanged” interface in the “PerfRecordClientContract” class. We use the “OnSessionCreated” method to start a timer that periodically pushes the current processor usage to the remote server:
using System;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Timers;
using BSAG.IOCTalk.Common.Exceptions;
using BSAG.IOCTalk.Common.Interface.Session;
using PerformanceRecorder.Interface;


namespace PerformanceRecorder.ClientConsole
{
    public class PerfRecordClientContract : ISessionStateChanged
    {
        private Timer pushTimer;
        private PerformanceCounter cpuCounter;

        public PerfRecordClientContract()
        {
            cpuCounter = new PerformanceCounter();
            cpuCounter.CategoryName = "Processor";
            cpuCounter.CounterName = "% Processor Time";
            cpuCounter.InstanceName = "_Total";

            pushTimer = new Timer(1000);
            pushTimer.Elapsed += OnPushTimer_Elapsed;
        }


        [Import]
        public IPerformanceObserverService ObserverService { get; set; }

        public void OnSessionCreated(ISession session)
        {
            pushTimer.Start();

            Console.WriteLine("Processor usage measurement started");
        }

        public void OnSessionTerminated(ISession session)
        {
            pushTimer.Stop();

            pushTimer.Dispose();
            cpuCounter.Dispose();
        }

        private void OnPushTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                ProcessorUsage pUsage = new ProcessorUsage()
                {
                    Time = DateTime.Now,
                    UsagePercent = (int)cpuCounter.NextValue(),
                    Source = Environment.MachineName
                };

                ObserverService.PublishProcessorUsage(pUsage);
            }
            catch (RemoteConnectionLostException)
            {
                Console.WriteLine("Connection lost during remote call");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unexpected exception: " + ex);
            }
        }
    }
}
  1. And here we go the client/server app in action (the high cpu load is not caused by the example Smile | :) IOC-Talk is designed and optimized for high throughput scenarios)

<img alt="" src="1095181/Console2.png" />

This is a very basic example with only one communication direction. The session management is only implemented within the client console application. In productive projects this might be implemented using your own functional session management interfaces outside the console app.

If you are interested try also the more complex sample application from the IOC-Talk project page:

https://ioctalk.codeplex.com

 

History

  • 2016-08-23: Upgraded Nuget package in PerformanceRecorder.zip to version 1.2.13 (Bugfix session creation)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here