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 a 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 new threads and pipe connections on demand. In addition to this, the server pipes are re-used across requests 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 such as 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, such as Connect and Close, to provide implementation specific to the client and server Named Pipes respectively. Both ClientPipeConnection and ServerPipeConnection have destructors that call the Dispose method to clean the unmanaged resources.
The pipe connection classes also contain a PipeHandle class that 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 that listens to client connections, and 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 re-using 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, parts of the code in the PipeManager Class are displayed and described.
The Initialize method creates a new thread that 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 to process a client request and generate 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 whether 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, 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.
——————————
Discuss this article at ivanweb.com