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

Inter-Process Communication in .NET Using Named Pipes, Part 2

0.00/5 (No votes)
14 Jun 2004 1  
This article explores a way of implementing Named Pipes based Inter-Process Communication between .NET applications

1. Introduction

Named Pipes are sections of shared memory used by separate processes to communicate with one another. The application that creates a pipe is the pipe server. A process that connects to the pipe server is a client.

In this article we will demonstrate how to create Named Pipe server and client applications that exchange text messages. Both applications use the Named Pipes .NET implementation outlined in Part 1.

The Named Pipes server is a multithreaded engine that serves concurrent requests by creating on demand new threads and pipe connections. In addition to this, the server pipes are reused across requests in order to reduce the resource consumption on the server.

It is important to note that the situations where this solution would be most beneficial is when one application is exchanging frequent short text messages with another, located on the same machine or within the same LAN. For structured data exchange those text messages can also be XML documents or serialized .NET objects.

2. Pipe Connections

Pipe connections are classes that encapsulate common Named Pipes operations like creating pipes, writing and reading data and others. The AppModule.NamedPipes assembly contains a base class for pipe connections, APipeConnection, which defines the common methods for reading and writing of data.

The APipeConnection abstract class also implements the IDisposable interface through the IInterProcessConnection interface, which is intended to ensure proper cleaning of unmanaged resources, namely the native pipe handles.

There are two other pipe connection classes that inherit from APipeConnection - ClientPipeConnection and ServerPipeConnection. They override some of the methods, like Connect and Close, to provide implementation specific to the client and server Named Pipes respectively. Both ClientPipeConnection and ServerPipeConnection have destructors, which call the Dispose method to clean the unmanaged resources.

The pipe connection classes also contain a PipeHandle class, which holds the operating system native handle and the current state of the pipe connection.

3. ServerNamedPipe Class

The Named Pipes server is a multithreaded engine that creates Named Pipes and handles client connections. There are two main classes that provide the server functionality PipeManager and ServerNamedPipe.

The ServerNamedPipe Class wraps a ServerPipeConnection and a separate Thread to keep it alive. Below is the ServerNamedPipe constrictor.

internal ServerNamedPipe(string name, uint outBuffer, 
     uint inBuffer, int maxReadBytes) {
  PipeConnection = new ServerPipeConnection(name, outBuffer, 
     inBuffer, maxReadBytes);
  PipeThread = new Thread(new ThreadStart(PipeListener));
  PipeThread.IsBackground = true;
  PipeThread.Name = "Pipe Thread " + 
     this.PipeConnection.NativeHandle.ToString();
  LastAction = DateTime.Now;
}

The constructor creates a new ServerPipeConnection and starts a new Thread that calls the PipeListener method. The main part of the latter is a loop, which listens to client connections, reads and writes data:

private void PipeListener() {
    CheckIfDisposed();
  try {
    Listen = Form1.PipeManager.Listen;
    Form1.ActivityRef.AppendText("Pipe " + 
       this.PipeConnection.NativeHandle.ToString() + 
       ": new pipe started" + Environment.NewLine);
    while (Listen) {
      LastAction = DateTime.Now;

Read data from the client pipe:

      string request = PipeConnection.Read();
      LastAction = DateTime.Now;
      if (request.Trim() != "") {

The PipeManager.HandleRequest method receives the client request, processes it and returns a response. This response is then written to the pipe:

        PipeConnection.Write(Form1.PipeManager.HandleRequest(request));
        Form1.ActivityRef.AppendText("Pipe " + 
          this.PipeConnection.NativeHandle.ToString() + 
          ": request handled" + Environment.NewLine);
      }
      else {
        PipeConnection.Write("Error: bad request");
      }
      LastAction = DateTime.Now;

Disconnect from the client pipe:

      PipeConnection.Disconnect();
      if (Listen) {
        Form1.ActivityRef.AppendText("Pipe " + 
           this.PipeConnection.NativeHandle.ToString() + 
           ": listening" + Environment.NewLine);

Start listening for a new client connection:

        Connect();
      }
      Form1.PipeManager.WakeUp();
    }
  } 
  catch (System.Threading.ThreadAbortException ex) { }
  catch (System.Threading.ThreadStateException ex) { }
  catch (Exception ex) { 
    // Log exception

  }
  finally {
    this.Close();
  }
}

Note that we do not close the server pipe. Creating a server Named Pipe is a relatively expensive operation so reusing the server pipes improves the performance and reduces the resource consumption on the server.

Our Named Pipes are created in blocking mode therefore the Connect method will block the current thread until a client connection comes through.

4. PipeManager Class

The PipeManager class is responsible for creating server pipes when necessary, manage the threads and also generate the response to the client requests. Below are displayed and described parts of the code in the PipeManager Class.

The Initialize method creates a new thread, which calls the Start method:

public void Initialize() {
  Pipes = Hashtable.Synchronized(_pipes);
  Mre = new ManualResetEvent(false);
  MainThread = new Thread(new ThreadStart(Start));
  MainThread.IsBackground = true;
  MainThread.Name = "Main Pipe Thread";
  MainThread.Start();
  Thread.Sleep(1000);
}

The PipeManager class creates new pipe connections and threads only on demand. This means that ServerPipeConnection objects are created only when no connections exist or all connection are busy responding to other requests. Normally 2-3 server Named Pipe instances can handle quite a big load of concurrent client requests. This however depends also on the time necessary for processing a client request and generating the response.

References to the created ServerPipeConnection objects are kept in the Pipes Hashtable.

private void Start() {
  try {
    while (_listen) {
      int[] keys = new int[Pipes.Keys.Count];
      Pipes.Keys.CopyTo(keys,0);

Loop through the existing ServerPipeConnection objects and check if they are still functional:

      foreach (int key in keys) {
        ServerNamedPipe serverPipe = (ServerNamedPipe)Pipes[key];
        if (serverPipe != null && 
             DateTime.Now.Subtract(serverPipe.LastAction).Milliseconds >
             PIPE_MAX_STUFFED_TIME && serverPipe.PipeConnection.GetState()
             != InterProcessConnectionState.WaitingForClient) {
          serverPipe.Listen = false;
          serverPipe.PipeThread.Abort();
          RemoveServerChannel(serverPipe.PipeConnection.NativeHandle);
        }
      }

This is determined by checking how long the pipe has been in a state other than "WaitingForClient".

The NumberPipes field contains the maximum number of server Named Pipes we want to have on the server:

      if (numChannels <= NumberPipes) {
        ServerNamedPipe pipe = new ServerNamedPipe(PipeName, OutBuffer, 
           InBuffer, MAX_READ_BYTES);
        try {

The Connect method puts the newly created pipe in listening mode, which blocks the thread until a client attempts to make a connection:

          pipe.Connect();
          pipe.LastAction = DateTime.Now;
          System.Threading.Interlocked.Increment(ref numChannels);

Start the ServerPipeConnection thread:

          pipe.Start();
          Pipes.Add(pipe.PipeConnection.NativeHandle, pipe);
        }
        catch (InterProcessIOException ex) {
          RemoveServerChannel(pipe.PipeConnection.NativeHandle);
          pipe.Dispose();
        }
      }
      else {
        Mre.Reset();
        Mre.WaitOne(1000, false);
      }
    }
  }
  catch {
    // Log exception

  }
}

5. Client Pipe Connections

To connect a client application to the server using Named Pipes we have to create an instance of the ClientPipeConnection class and use its methods for reading and writing data. The following code illustrates that:

IInterProcessConnection clientConnection = null;
  try {
    clientConnection = new ClientPipeConnection("MyPipe", ".");
    clientConnection.Connect();
    clientConnection.Write(textBox1.Text);
    clientConnection.Close();
  }
  catch {
    clientConnection.Dispose();
  }

The name of the pipe "MyPipe" needs to be the same as the name used to create the server pipe. If the Named Pipes server is on the same machine ,then the second parameter of the ClientPipeConnection constructor is ".". Otherwise it needs to be the network name of the server machine.

It is important to always call the Dispose method when we have finished working with the client pipe. This will release the related unmanaged resources, but more importantly, will allow the server pipe to disconnect from the client and start waiting for new connections. If the client pipe is not closed the server one will be indefinitely blocked until the Named Pipes server is shut down.

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