Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Many to One Local IPC using WCF and NetNamedPipeBinding

5.00/5 (13 votes)
8 Mar 2010CPOL4 min read 1   2.5K  
I needed to talk between one server process and multiple client processes, this was a simple test harness to prove it

Introduction

I used WCF with the NetNamedPipeBinding for IPC between my server process and multiple clients that would check in and register with the server.

The clients needed to be able to invoke methods on the server - with or without args and return values, and the server needed to be able to invoke methods on the client in similar ways.

In the real world application example, I didn't have a way for the clients to unregister before they exited, so that was done server side.

Here is a picture of it in operation.

Both clients have registered - you can see their GUIDs in the listbox on the server.

One of the clients has sent a message in, the other has sent an anonymous message, then the first client has requested the anonymous message. 

The server has sent a broadcast message to all registered clients.

serverclient.jpg

Background

The WCF stuff seemed very simple to begin with, however I found before long that I had to trawl through a few articles to get things working correctly - and as my first foray into WCF, it may not be completely best practice - I hope I learn from comments if it isn't!

To understand this, you don't need to understand any details about named pipes other than that they are something that runs locally on your computer, and that WCF sits above it and hides all of the details.

The most important thing is to understand that a WCF communication consists of two endpoints (a client and a server), and a channel that allows communication to flow in between them. In this case, the channel is using a named pipe to facilitate transport of messages.

Additionally none of this WCF is like COM - it doesn't require marshalling or serialization of message objects, and if you invoke an object method on the server, it is running it inside the process that is hosting the server and returning the result - you don't really need to worry about how it is happening - well, I didn't anyway.

Using the Code

The code is really not useful other than to see as a working example, it isn't something you would link into an existing application, and probably isn't even a very good starting point for a new application, however it is what it is and allows you to watch what is going on under several different messaging scenarios.

The first part is messages that start on the client - usually the first message would be a "Register" message that gives a Guid to identify the client to the server. I mark this as OneWay so that WCF knows not to wait for a reply.

C#
[ServiceContract(SessionMode = SessionMode.Allowed)]
public interface IFromClientToServerMessages
{
    [OperationContract(IsOneWay = true)]
    void Register(Guid clientID);

    [OperationContract(IsOneWay = true)]
    void DisplayTextOnServer(string text);

    [OperationContract(IsOneWay = true)]
    void DisplayTextOnServerAsFromThisClient(Guid clientID, string text);

    [OperationContract]
    string GetLastAnonMessage();
}

Next is messages that start on the server and go out to the clients - in this case, there is only one - a simple instruction to display some text.

C#
[ServiceContract(SessionMode = SessionMode.Allowed)]
public interface IFromServerToClientMessages
{
    [OperationContract(IsOneWay = true)]
    void DisplayTextInClient(string text);
}

Clients are started with the GUID as part of their URI. The server is much the same but just called "Server" (i.e. no GUID). Note that all I need to do is implement the interface and start a service host for the object, add a service endpoint, and open the connection. It is then able to receive channel connections. At exit, there really should be a corresponding Dispose that closes it down too, but I've left that out of the example.

C#
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, 
	InstanceContextMode = InstanceContextMode.Single)]
public partial class ClientForm : Form, IFromServerToClientMessages
{
    Guid _clientID;
    ServiceHost _clientHost;

    public ClientForm()
    {
        InitializeComponent();

        _clientID = Guid.NewGuid();
        _clientHost = new ServiceHost(this);
        _clientHost.AddServiceEndpoint((typeof(IFromServerToClientMessages)), 
		new NetNamedPipeBinding(), "net.pipe://localhost/Client_" + 
		_clientID.ToString());
        _clientHost.Open();
    }

A basic message from client to server has the following code - articles I read by others in other forums implied that closing the channel between messages was the way to go.

C#
public void Register(Guid clientID)
{
    using (ChannelFactory<IFromClientToServerMessages> factory = 
	new ChannelFactory<IFromClientToServerMessages>(new NetNamedPipeBinding(), 
	new EndpointAddress("net.pipe://localhost/Server")))
    {
        IFromClientToServerMessages clientToServerChannel = factory.CreateChannel();

        try
        {
            clientToServerChannel.Register(clientID);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }
        finally
        {
            CloseChannel((ICommunicationObject)clientToServerChannel);
        }
    }
}

private void CloseChannel(ICommunicationObject channel)
{
    try
    {
        channel.Close();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
    finally
    {
        channel.Abort();
    }
}

Points of Interest

Closing the channel is important - you can easily get a sample working, but before long you find that after 10 minutes, you start hitting timeouts. Then you go down the path of increasing the timeout or trying to use reliable session, but NetNamedPipeBinding is a bit different. Next you try keepalives, but you find that if you step through it with the debugger you hit the timeouts quite easily. Then you read more forums and come to the conclusion that closing it each time isn't too much of an overhead - sure a lot of copy pasted code, but it works. Well that's how it was for me anyhow, your mileage may vary!

Also, if you get a timeout exception, it means that the other end of the channel did not respond within a minute - this usually means that you've got something trying to talk back the other way at the same time that is blocking. The exceptions look like this:

 System.TimeoutException: The read from the pipe did not complete 
	within the allotted timeout of 00:01:00. The time allotted to this 
	operation may have been a portion of a longer timeout. ---> 
	System.IO.IOException: The read operation failed, see inner exception. ---> 
	System.TimeoutException: The read from the pipe did not complete within the 
	allotted timeout of 00:01:00. The time allotted to this operation may have 
	been a portion of a longer timeout.
   at System.ServiceModel.Channels.PipeConnection.WaitForSyncRead
	(TimeSpan timeout, Boolean traceExceptionsAsErrors)
   at System.ServiceModel.Channels.PipeConnection.Read(Byte[] buffer, 
	Int32 offset, Int32 size, TimeSpan timeout)
   at System.ServiceModel.Channels.DelegatingConnection.Read(Byte[] buffer, 
	Int32 offset, Int32 size, TimeSpan timeout)
   at System.ServiceModel.Channels.ConnectionStream.Read(Byte[] buffer, 
	Int32 offset, Int32 count, TimeSpan timeout)
   at System.ServiceModel.Channels.ConnectionStream.Read(Byte[] buffer, 
	Int32 offset, Int32 count)
   at System.Net.FixedSizeReader.ReadPacket(Byte[] buffer, Int32 offset, Int32 count)
   at System.Net.Security.NegotiateStream.StartFrameHeader(Byte[] buffer, 
	Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.NegotiateStream.StartReading(Byte[] buffer, 
	Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.NegotiateStream.ProcessRead(Byte[] buffer, 
	Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
   --- End of inner exception stack trace ---
   at System.Net.Security.NegotiateStream.ProcessRead(Byte[] buffer, 
	Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.NegotiateStream.Read(Byte[] buffer, 
	Int32 offset, Int32 count)
   at System.ServiceModel.Channels.StreamConnection.Read(Byte[] buffer, 
	Int32 offset, Int32 size, TimeSpan timeout)
   --- End of inner exception stack trace ---
Server stack trace: 
   at System.ServiceModel.Channels.StreamConnection.Read(Byte[] buffer, 
	Int32 offset, Int32 size, TimeSpan timeout)
   at System.ServiceModel.Channels.SessionConnectionReader.Receive(TimeSpan timeout)
   at System.ServiceModel.Channels.SynchronizedMessageSource.Receive(TimeSpan timeout)
   at System.ServiceModel.Channels.FramingDuplexSessionChannel.OnClose
	(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.OnClose(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Close()
Exception rethrown at [0]: 
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage
	(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke
	(MessageData& msgData, Int32 type)
   at System.ServiceModel.ICommunicationObject.Close()

History

  • 8th March, 2010: Article first cut written

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)