Introduction
The following tip shows how to implement a Named Piped Client/Server using asynchronous methods for communications between .NET applications.
Background
I created this article because I found it hard to find good examples of Named Pipe Client/Server that use async methods. I found a great article on CodeProject by "Maddog Mike B" but it only worked on .NET 3.5, so I had to come up with a new version for .NET 4.5. Most Named Pipe examples seem to be based on synchronous pipes spliced with threading to create asynchronous Pipes. I don't know if the method I provide is correct but it's what has resulted after hours of scouring the net piecing together scraps of information.
Overview
This code was tested with Visual Studio 2012 and .NET 4.5. The project is divided into two winforms based projects, PipeTestClient
(sends messages to the server) and PipeTestServer
(listens for incoming messages from clients). Both projects have been supplied in zip format.
The AsyncCallback
method is called when the method completes. In .NET, events can trigger delegates, but also methods can trigger delegates. The AsyncCallback
class allows methods to start an asynchronous function and supply a delegate method to call when the asynchronous function completes.
The state object can be used to pass information between the asynchronous function call and the corresponding AsyncCallback
method.
Async Listen Method [Listen Server Class]
The Listen()
method is called taking one argument - PipeName
, this is assigned to a class level var
for use later in a recursive function. The NamedPipeServerStream
is created using the PipeOptions.Asynchronous
argument (needed for async operation). The passed in PipeName
is also used. (Must be the same name as the Client). The Servers asynchronous BeginWaitFoConnection()
method is called using AsyncCallback
to trigger the WaitForConnectionCallback
method, which also receives the state
object - in this case the original Pipe. Once into the <span style="FONT-FAMILY: 'Segoe UI', Arial, sans-serif; COLOR: rgb(17,17,17); FONT-SIZE: 14px"></span><code>WaitForConnectionCallback
method, the passed state pipe is allocated to a local var
and waiting for the connection is ended. A read buffer is created and the message data is read into and converted to a string
type. The Original server pipe is then killed and a new one created using the same criteria and the original, the new server pipe begins to wait for another connection using its own method as the AsyncCallback
Function (recursion).
public delegate void DelegateMessage(string Reply);
class PipeServer
{
public event DelegateMessage PipeMessage;
string _pipeName;
public void Listen(string PipeName)
{
try
{
_pipeName = PipeName;
NamedPipeServerStream pipeServer = new NamedPipeServerStream(PipeName,
PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
pipeServer.BeginWaitForConnection
(new AsyncCallback(WaitForConnectionCallBack), pipeServer);
}
catch (Exception oEX)
{
Debug.WriteLine(oEX.Message);
}
}
private void WaitForConnectionCallBack(IAsyncResult iar)
{
try
{
NamedPipeServerStream pipeServer = (NamedPipeServerStream)iar.AsyncState;
pipeServer.EndWaitForConnection(iar);
byte[] buffer = new byte[255];
pipeServer.Read(buffer, 0, 255);
string stringData = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
Debug.WriteLine(stringData + Environment.NewLine);
PipeMessage.Invoke(stringData);
pipeServer.Close();
pipeServer = null;
pipeServer = new NamedPipeServerStream(_pipeName, PipeDirection.In,
1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
pipeServer.BeginWaitForConnection(
new AsyncCallback(WaitForConnectionCallBack), pipeServer);
}
catch
{
return;
}
}
}
Async Send Method [PipeClient Class]
The Send()
method is called and takes Send String, Pipe name and connection timeout to the server in milliseconds as arguments. Moving through the code, a NamedPipedClientStream
(Pipe) is created using the PipeOptions.Asynchronous
argument, this is vital if we are going to succeed in sending an async pipe message. We also use the passed in PipeName
(this should be the same name as the server is using). Once created, we try to connect to the server, if the timeout argument is left out and the server does not exist, then the Connect
method will sit and wait indefinitely, so it makes sense to use a timeout (in milliseconds). A byte array is then created and the send string/message is converted to it. At this point, assuming a successful connection, the clients asynchronous BeginWrite()
method is called using AsyncCallback
to trigger the AsyncSend
method, which also receives the state
object - in this case the original Pipe. Once into the AsyncSend
method, the passed state pipe is allocated to a local var
and the write is ended.
class PipeClient
{
public void Send(string SendStr, string PipeName, int TimeOut = 1000)
{
try
{
NamedPipeClientStream pipeStream = new NamedPipeClientStream
(".", PipeName, PipeDirection.Out, PipeOptions.Asynchronous);
pipeStream.Connect(TimeOut);
Debug.WriteLine("[Client] Pipe connection established");
byte[] _buffer = Encoding.UTF8.GetBytes(SendStr);
pipeStream.BeginWrite
(_buffer, 0, _buffer.Length, new AsyncCallback(AsyncSend), pipeStream);
}
catch (TimeoutException oEX)
{
Debug.WriteLine(oEX.Message);
}
}
private void AsyncSend(IAsyncResult iar)
{
try
{
NamedPipeClientStream pipeStream = (NamedPipeClientStream)iar.AsyncState;
pipeStream.EndWrite(iar);
pipeStream.Flush();
pipeStream.Close();
pipeStream.Dispose();
}
catch (Exception oEX)
{
Debug.WriteLine(oEX.Message);
}
}
}
Usage
The Server code also includes event delegates to message back to the calling form - included is a threadsafe method for cross thread communication - please see the code.
Start both the client and server apps, Click Listen on the server App. Then click send as many times as you like on the client app. The messaging is instant and reliable.
History
Help
If anyone has any tips or tricks for making this cleaner/faster, please let me know.