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) {
}
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 {
}
}
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.