Tuesday, February 21, 2012

Inter-Process Duplex Communication with WCF Named Pipes

Named pipes enable two applications on the same windows-based physical device to communicate with each other across process boundaries. This blog entry shows how to use WCF and the NetNamedPipeBinding to set up a duplex communication channel based on named pipes.

A named pipe itself is just an object in the windows kernel. It provides access for different clients to a shared resource. A pipe is identified by its name and can be used for one-way or duplex communication scenarios. While there may be only one pipe server, there may be more than one client applications using a pipe. With the NetNamedPipeBinding, WCF offers an abstraction to pipe objects, making it really easy to use them for inter-process communication.

I created a simple example to show you the setup of two applications communicating via named pipes. Let’s assume that one application – the pipe server – can be used to add products and stores new product information locally. Another application on the same machine needs to know when a new product was added and eventually needs to retrieve further product information. We start by setting up the contract both applications will use:


[ServiceContract(SessionMode = SessionMode.Required,
    CallbackContract = typeof(IProductServiceCallback))]
public interface IProductService
{
    [OperationContract]
    void RegisterForAddedProducts();
    [OperationContract]
    void GetProductInformation(string productNumber);
}

The service contract contains two methods. The first one can be used by the client to register itself for notification of new products. The second method can be used to request further product information by passing the product number. Both methods do not have a return value. In order to pass information back to the client, we set the SessionMode and CallbackContract properties of the ServiceContract attribute. The callback contract defines the methods that must be present on the client calling the service in order to get messages from the server. Let’s have a look at the callback contract:
public interface IProductServiceCallback
{
    [OperationContract(IsOneWay = true)]
    void ProductAdded(string productNumber);
    [OperationContract(IsOneWay = true)]
    void ProductInformationCallback(Product product);
}

Not surprisingly, the callback contract also contains two methods. ProductAdded gets called by the server, when a new product was added. The ProductInformationCallback will be called from the server after the client requested further information and the server had enough time to retrieve them. Instead I could just have defined a return value in the service to pass the product back to the client but this scenario shows a great benefit of the callback mechanism. It’s asynchronous by default and therefore doesn’t freeze the client and gives the server enough time to process the request. This is ideal for longer taking requests. Notice that both methods are marked as one way. The second method uses Product as parameter which is no built in CLR object. Here is the very short definition of a product:
public class Product
{
    string Number { get; set; }
    string Name { get; set; }
    decimal Price { get; set; }
    string Manufacturer { get; set; }
}

That’s all for the contract. Let’s go on with the pipe server.
The Server
The server needs to implement the service contract:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class ProductService : IProductService

The service behavior needs to specify the instance context mode as single. This together with the session mode on the contract is required to support duplex communication. But there is another reason. I’d like to pass an instance of the host application to the service implementation in order to pass calls from a service request to the application that can handle the request and provide appropriate information. Therefore we define the following constructor for the service implementation:
public ProductService(IHostApplication hostApplication)
{
    addedProductsCallbackList = new List<IProductServiceCallback>();
    this.hostApplication = hostApplication;
    this.hostApplication.ProductAdded += HostApplicationProductAdded;
}

The host application is indicated by a simple interface. It defines an event to notify listeners when a new product was added. The service itself holds a list of callbacks for registered clients. When a new product was added the service iterates of the callback list and invokes the ProductAdded method passing it the product number:
void HostApplicationProductAdded(object sender, EventArgs<string> e)
{
    foreach (var productServiceCallback in addedProductsCallbackList)
    {
        productServiceCallback.ProductAdded(e.Data);
    }
}

The client subscribes itself through the RegisterForAddedProducts method of the service:
public void RegisterForAddedProducts()
{
    var callback = OperationContext.Current.
                   GetCallbackChannel<IProductServiceCallback>();
    if (callback != null)
    {
        this.addedProductsCallbackList.Add(callback);
    }
}

You can retrieve a callback channel by calling the generic GetCallbackChannel method of the current instance of the OperationContext. The context contains information regarding the current service call. If you want to see the GetProductInformation method, please download the sample code. I will leave it out right now.
What we now need to do is to start the service. Let’s first have a look at the endpoint definition from the configuration file:
<system.serviceModel>
  <services>
    <service name="IProductService">
      <endpoint address="net.pipe://localhost/ProductService"
      binding="netNamedPipeBinding"
      contract="WCFNamedPipesExample.Services.IProductService"
      name="ProductServiceEndpoint" />
    </service>
  </services>
</system.serviceModel>

This is a very simple configuration. No additional behaviors or anything else. We can use this configuration to start the service from our host application:
private void StartHost()
{
    var group =
        ServiceModelSectionGroup.GetSectionGroup(
        ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None));
    if (group != null)
    {
        var service = group.Services.Services[0];
        var baseAddress = service.Endpoints[0].Address.AbsoluteUri.
Replace(service.Endpoints[0].Address.AbsolutePath, String.Empty);
        var productService = new ProductService(this);
        var host = new ServiceHost(productService, new[] { new Uri(baseAddress) });
        host.AddServiceEndpoint(typeof(IProductService),
                                new NetNamedPipeBinding(),
                                service.Endpoints[0].Address.AbsolutePath);
        try
        {
            host.Open();
        }
        catch (CommunicationObjectFaultedException cofe)
        {
            //log exception or do something appropriate
        }
        catch (TimeoutException te)
        {
            //log exception or do something appropriate
        }
    }
}

In this case there is only one service with only one endpoint. So we could directly access the first element in the collections. You could also iterate over services and endpoint to create the objects and hosts you need. We start the listening on the pipe endpoint by calling host.Open(). It’s always a good idea to do this inside a try/catch block.
The Client
With the setup so far we have a running host listening for requests from a client. So let’s move on to the realization a simple client. The client is another WPF application running on the same machine. I will start by defining the client endpoint in the configuration file:
<system.serviceModel>
  <client>
    <endpoint address="net.pipe://localhost/ProductService"
      binding="netNamedPipeBinding"
      contract="WCFNamedPipesExample.Services.IProductService"
      name="ProductServiceEndpoint" />
  </client>
</system.serviceModel>

As you can see, it’s nearly the same as for the service description on the server side. With the configuration we can now create a pipe proxy in our client application in order to call server side methods. To get messages back from the server we also need to implement the callback interface:
public class ClientController : IProductServiceCallback

The creation of the pipe proxy is done in the following method:
private void Connect()
{
    var group = ServiceModelSectionGroup.GetSectionGroup(
        ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None));
    if (group != null)
    {
        //create duplex channel factory
        pipeFactory = new DuplexChannelFactory<IProductService>(
                                      this, group.Client.Endpoints[0].Name);
        //create a communication channel and register for its events
        pipeProxy = pipeFactory.CreateChannel();
        ((IClientChannel) pipeProxy).Faulted += PipeProxyFaulted;
        ((IClientChannel) pipeProxy).Opened += PipeProxyOpened;
        try
        {
            //try to open the connection
            ((IClientChannel) pipeProxy).Open();
            this.checkConnectionTimer.Stop();
            //register for added products
            pipeProxy.RegisterForAddedProducts();
        }
        catch
        {
            //for example show status text or log;
        }
    }
}

To create a duplex channel we can use the generic constructor of the DuplexChannelFactory. The constructor takes an instance of the callback contract, which is in this case the client controller itself, and the name of an endpoint. With the pipe factory you can then create a concrete channel. Subscribe to the channel events to notify the user if the channel was unexpectedly closed or to start recovery scenarios. Within the try/catch block we open the channel and register ourselves to the server to get notified of new products. The client uses a timer (checkConnectionTimer) which repeatedly tries to connect to the server until a connection was established. So, even if the server is not up the client will attempt to connect. As soon as the server starts the client can automatically connect.
When a new product was added the server calls the ProductAdded method on the client passing it the product number. In the sample application I just show a message box with the product number:
public void ProductAdded(string productNumber)
{
    MessageBox.Show("Received Product Number: "
                    + productNumber, "Product Added!");
}

Again I will leave out the product information part. You can check it out in the sample code. That’s all! With this setup you get a duplex pipe channel with a client calling asynchronous methods on the server and a server using callbacks to invoke methods on the client passing the requested information. Here is a screenshot of the sample server and client applications with the product added callback invoked.


Both applications are WPF applications. WPF application architecture was not in the focus of this article, so I made it as simple as possible. Please don’t blame me on not using MVVM or the like.

Download Sample Code:

13 comments:

  1. This example is very well done, but where is the link to download the code?

    ReplyDelete
    Replies
    1. The link to the sample code is attached at the end of the blog entry. Here is the full link just in case you're not able to see the folder icon above: https://skydrive.live.com/redir.aspx?cid=e7d1cd997e197aa3&resid=E7D1CD997E197AA3!329&parid=E7D1CD997E197AA3!141&authkey=!AFvGGJN67Pfd3UM.
      When you reach the SkyDrive folder just click on the example again.

      Delete
    2. Hmm no folder icon in chrome but it's showing up fine in other browsers for me. Thanks for the link.

      Delete
  2. Thank you for this very interesting blog. What do you think is the best way to prevent an error on the Host if a registered Client has shut down? I want to use a similar design but the Host must be very robust.

    ReplyDelete
    Replies
    1. Hi JC,

      there are several possibilities. The client may just deregister on closing, but that's often not enough because client can shutdown unexpectedly. You can surround the call to the callback channel with a try/catch and remove the client if an exception is thrown, but that's also not the best solution because exceptions are expensive. To extend the given example you can just query the current connection status of the underlying channel and decide what to do next. I extende the event handler for added products on the host as follows:

      void HostApplicationProductAdded(object sender, EventArgs e)
      {
      //notify all clients
      foreach (var productServiceCallback in addedProductsCallbackList)
      {
      var channel = productServiceCallback as IServiceChannel;
      if(channel != null)
      {
      if(channel.State == CommunicationState.Opened)
      {
      productServiceCallback.ProductAdded(e.Data);
      }
      }
      }
      }

      The callback implements the IServiceChannel interface which contains all the necessary informations about the underlying communication object.

      Hope this helps! Regards, Dani

      Delete
  3. The thing I liked with this is, inter-process communication from same OS instance, not from outside of the system.
    Can I get the link to download source code to try out...

    ReplyDelete
    Replies
    1. Hi, the link to the sample code is directly below the article. In case you don't see it (as others had the same Problems too), I'll just post it here:
      https://skydrive.live.com/?cid=e7d1cd997e197aa3&id=E7D1CD997E197AA3!329&authkey=!AFvGGJN67Pfd3UM#
      Just use the download button in the Sky Drive window that appears.

      Delete
  4. Hi Daniel,
    This is really very good post. Actually i started implementing the Named pipe with this topic itself. It is really working fine to me except a small hitch. If i leave both the server and client idle for like 10 mins and then try to send the data from the either the server or client, it doesn't work. On the server it gives me the exception CommunicationObjectAbortedException. Can you please put some light on it?

    ReplyDelete
  5. This comment has been removed by a blog administrator.

    ReplyDelete
  6. This is my first visit to your blog, your post made productive reading, thank you. dot net training in chennai

    ReplyDelete
  7. You post explain everything in detail and it was very interesting to read. Thank you. nata coaching centres in chennai

    ReplyDelete