Jump to content
Aerosol

IPC with Named Pipes

Recommended Posts

Posted

NamedPipes Source

Introduction

This article describes a messaging library which can be used to send a message between two .Net applications running on the same network. The library allows a simple string to be sent between a Client and Server application.

The library uses Named Pipes in it's internal implementation. For more information on Named Pipes visit the MSDN page.

I also used the following Code Project articles as a starting point for developing the library:

Background

I recently came across a scenario where I needed to notify an application that an upgrade was waiting to start. I needed a simple mechanism that would allow me to signal the application. Once signalled, the application would safely shutdown, allowing the upgrade to start.

I investigated a number of IPC solution before developing this library. I settled for Named Pipes as it was the most light weight method of IPC I could find.

Using the code

The library is simple to use, there are two main entry points the PipeServer and the PipeClient. The following code snippet is copied from the sample application included in the source code:

var pipeServer = new PipeServer("demo", PipeDirection.InOut);
pipeServer.MessageReceived += (s, o) => pipeServer.Send(o.Message);
pipeServer.Start();

var pipeClient = new PipeClient("demo", PipeDirection.InOut);
pipeClient.MessageReceived += (s, o) => Console.WriteLine("Client Received: value: {0}", o.Message);
pipeClient.Connect();

The sample application demonstrates using the library to build a simple echo server. The MessageReceived event handler simply echoes any messages received from the client.

PipeServer

Start

The start method calls BeginWaitingForConnection passing a state object. The Start method is overloaded allowing the client to provide a cancellation token.

public void Start(CancellationToken token)
{
if (this.disposed)
{
throw new ObjectDisposedException(typeof(PipeServer).Name);
}

var state = new PipeServerState(this.ServerStream, token);
this.ServerStream.BeginWaitForConnection(this.ConnectionCallback, state);
}

Stop

The stop method simply calls the Cancel method of the internal CancelllationTokenSource. Calling Cancel sets the IsCancelationRequested property of the token which gracefully terminates the Server.

public void Stop()
{
if (this.disposed)
{
throw new ObjectDisposedException(typeof(PipeServer).Name);
}

this.cancellationTokenSource.Cancel();
}

Send

The send method first converts the provided string to a byte array. The bytes are then written to the stream using the BeginWrite method of the PipeStream.

public void Send(string value)
{
if (this.disposed)
{
throw new ObjectDisposedException(typeof(PipeClient).Name);
}

byte[] buffer = Encoding.UTF8.GetBytes(value);
this.ServerStream.BeginWrite(buffer, 0, buffer.Length, this.SendCallback, this.ServerStream);
}

ReadCallback

The ReadCallback method is called when data is received on the incoming stream. The received bytes are first read from the stream, decoded back to a string and stored in the state object.

If the IsMessageComplete property is set, this indicates that the Client has finished sending the current message. The MessageRecevied event in invoked and the Message buffer is cleared.

If the server has not stopped - indicated by the cancellation token - and the client is still connected, the server continues reading data, otherwise the server begins waiting for the next connection.

private void ReadCallback(IAsyncResult ar)
{
var pipeState = (PipeServerState)ar.AsyncState;

int received = pipeState.PipeServer.EndRead(ar);
string stringData = Encoding.UTF8.GetString(pipeState.Buffer, 0, received);
pipeState.Message.Append(stringData);
if (pipeState.PipeServer.IsMessageComplete)
{
this.OnMessageReceived(new MessageReceivedEventArgs(stringData));
pipeState.Message.Clear();
}

if (!(this.cancellationToken.IsCancellationRequested || pipeState.ExternalCancellationToken.IsCancellationRequested))
{
if (pipeState.PipeServer.IsConnected)
{
pipeState.PipeServer.BeginRead(pipeState.Buffer, 0, 255, this.ReadCallback, pipeState);
}
else
{
pipeState.PipeServer.BeginWaitForConnection(this.ConnectionCallback, pipeState);
}
}
}

PipeClient

Connect

The connect method simply establishes a connection to the PipeServer, and begins readings the first message received from the server. An interesting caveat is that you can only set the ReadMode of the ClientStream once a connection has been established.

public void Connect(int timeout = 1000)
{
if (this.disposed)
{
throw new ObjectDisposedException(typeof(PipeClient).Name);
}

this.ClientStream.Connect(timeout);
this.ClientStream.ReadMode = PipeTransmissionMode.Message;

var clientState = new PipeClientState(this.ClientStream);
this.ClientStream.BeginRead(
clientState.Buffer,
0,
clientState.Buffer.Length,
this.ReadCallback,
clientState);
}

Send

The send method for the PipeClient is very similar to the PipeServer. The provided string is converted to a byte array and then written to the stream.

public void Send(string value)
{
if (this.disposed)
{
throw new ObjectDisposedException(typeof(PipeClient).Name);
}

byte[] buffer = Encoding.UTF8.GetBytes(value);
this.ClientStream.BeginWrite(buffer, 0, buffer.Length, this.SendCallback, this.ClientStream);
}

ReadCallback

The ReadCallback method is again similar to the PipeServer.ReadCallback method without the added complication of handling cancellation.

private void ReadCallback(IAsyncResult ar)
{
var pipeState = (PipeClientState)ar.AsyncState;
int received = pipeState.PipeClient.EndRead(ar);
string stringData = Encoding.UTF8.GetString(pipeState.Buffer, 0, received);
pipeState.Message.Append(stringData);
if (pipeState.PipeClient.IsMessageComplete)
{
this.OnMessageReceived(new MessageReceivedEventArgs(pipeState.Message.ToString()));
pipeState.Message.Clear();
}

if (pipeState.PipeClient.IsConnected)
{
pipeState.PipeClient.BeginRead(pipeState.Buffer, 0, 255, this.ReadCallback, pipeState);
}
}

Points of Interest

NamedPapes vs AnonymousPipes

The System.IO.Pipes namespace contains a managed API for both AnonymousPipes and NamedPipes. The MSDN page states the following:

Anonymous pipes

Anonymous pipes are one-way and cannot be used over a network. They support only a single server instance. Anonymous pipes are useful for communication between threads, or between parent and child processes where the pipe handles can be easily passed to the child process when it is created

Named pipes

Named pipes provide interprocess communication between a pipe server and one or more pipe clients. Named pipes can be one-way or duplex. They support message-based communication and allow multiple clients to connect simultaneously to the server process using the same pipe name.

I based the library on Named pipes because I wanted the library to support duplex communication. Also, the parent child model of Anonymous pipes didn't fit with my scenario.

PipeTansmissionMode

Named pipes offer two transmission modes, Byte mode and Message mode.

  • In Byte mode - messages travel as a continuous stream of bytes between the client and server.
  • In Message mode - the client and the server send and receive data in discrete units. The end of a message is indicated by setting the IsMessageComplete property.

In both modes a write on one side will not always result in a same-size read on the other. This means that a client application and a server application do not know how many bytes are being read from or written to a pipe at any given moment.

In Byte mode the end of a complete message could be identified by searching for and End-Of-Message marker. This would require defining an application level protocol which would add needless complexity to the library.

In message mode the end of a message can be identified by reading the IsMessageComplete property. The IsMessageComplete property is set to true by calling Read or EndRead.

Source Code

If you would like to view the libraries source code and demo applications the code can be found on my Bit Bucket site.

Source

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...