Anonymous Pipes:
Anonymous pipes can not be used over a network. Anonymous pipes are for one-way messaging. Anonymous pipes don't support message mode. They support inter-process communication mainly between a process and another process which is invoked by it. A reference of PipeStream object is passed to child process when invoking the child process.
Named Pipes
They support one-way and both-way (duplex) messaging. As their name implies, they use name of pipes for messaging. The name of the pipe should be system-wide unique. We will be using named pipes for our examples in this post.
As we discussed above, PipeStream is a mechanism for inter-process communication. These processes don't have to be on the same computer. NamedPipeClientStream has constructors to support servers on different computers. We just need to specify the Server name in those constructors and we should be good to go.
Namespace:
System.IO.Pipes
Named pipes can be used between processes on the same or other computers across the Windows network.
Communication between Client and Server:
Let's consider a scenario in which a server needs to send a single message to a client using PipeStream. They are in different processes and we are using Named version of Server and Client.
Before draining messages through the pipe, a server needs to have a connection established with a Client. If there is no request, it must wait for the connection. In the code below, a server is waiting for a connection using server's WaitForConnection. As soon as the connection is established, it writes through the pipe.
private void btnSendMessage_Click(object sender, RoutedEventArgs e)
{
using (var namedPipeStream =
new NamedPipeServerStream("namedPipeSynch", PipeDirection.InOut, 1, PipeTransmissionMode.Message))
{
namedPipeStream.WaitForConnection();
byte[] messageBytes = Encoding.UTF8.GetBytes(this.textBox1.Text);
namedPipeStream.Write(messageBytes, 0, messageBytes.Length);
}
}
The client needs to connect to the Server using Client's Connect method. This is a blocking call, it means that it has to wait until a connection is established. As soon as the connection is established, it can attempt to read as follows:
private void btnGetMessage_Click(object sender, RoutedEventArgs e)
{
using (var namedPipeClientStream = new NamedPipeClientStream(".", "namedPipeSynch", PipeDirection.InOut))
{
namedPipeClientStream.Connect();
namedPipeClientStream.ReadMode = PipeTransmissionMode.Message;
StreamReader streamReader = new StreamReader(namedPipeClientStream);
this.textBlockMessage.Text = streamReader.ReadToEnd();
}
}
Sending messages before a connection is established:
The communication must be established between client and server before writing message on the stream. If it is attempted to write a message before a connection is made, it results in InvalidOperationException. In the following image, a message is attempted to be pass through the pipe without calling WaitForConnection, resulting in the exception with appropriate error message.
Named Pipes between Two Clients:
Two NamedPipeClientStream can not communicate with each other.
Transmission Mode:
There are two available pipe transmission modes for PipeStream. They are as follows:
1. Byte
2. Message
It is a read-only property on the server. It can be specified in the constructor while instantiating the Server. For client, we can specify it after connecting it to the server before any transmission takes place.
Pipe Direction:
It is weird but I have noticed that for message mode transmission using PipeStream both Client and Server should be specified with PipeDirection as InOut. So if Server has PipeDirection as Out and Client is specified with In for direction, it would result in an UnauthorizedAccessException with familiar Access to the path is denied message.
Connecting a Non-Existing PipeStream Server:
We can specify name of the NamedPipeStreamServer to connect with it. It is possible that server is not available when we attempt to connect it. It must be remembered that this is a blocking call, the client would keep on waiting until the server becomes available. If you are attempting to connect on a UI thread then the UI thread keeps blocking. It is advisable to not make such calls on UI thread. It is also advisable to use an overload of Connect with some TimeOut value. If connection is unsuccessful, it results in a TimeOutException. We can also verify if the connection is successful using the IsConnected property of PipeStream. Since this is defined on PipeStream level, this is available for both Client and Server (Named and anonymous).
using (var namedPipeClientStream = new NamedPipeClientStream(".", "namedPipe", PipeDirection.InOut))
{
try
{
namedPipeClientStream.Connect(1000);
namedPipeClientStream.ReadMode = PipeTransmissionMode.Message;
StreamReader streamReader = new StreamReader(namedPipeClientStream);
this.textBlockMessage.Text = streamReader.ReadToEnd();
}
catch (TimeoutException ex)
{
MessageBox.Show(ex.Message);
}
}
In this example, Client would just blocks for 1 second for a server. If it is not able to connect to a server during that period, it would result in TimeOutException.
It must be remembered that there is no such overload available in the server which supports a TimeOut when waiting for connection.
Synchronous Vs ASynchronous Transmission:
PipeStream supports both Synchronous and Asynchronous transmission. This can be specified by using PipeOptions property of PipeStream.
The client and server can be individually set as synchronous / asynchronous i.e. it is possible that either of client / server is working in synch mode while the other is operating otherwise. The other most important thing is that PipeStream implements IDisposable. If you are using an Asynchronous operations like BeginWaitForConnection then as the object is disposed, it would execute the callback before even a connection is made. Now since the Server is already disposed, attempting to write something on the PipeStream or even just calling EndWaitForConnection would result in ObjectDisposedException. So we must be careful in using asynchronous operations for a PipeStream in specially using block as it would dispose the object when it finishes.
In order to do that, we would need to have a code like this:
private void btnSendAsynchMessage_Click(object sender, RoutedEventArgs e)
{
string MessageToSend = this.textBox1.Text;
if (!namedPipeStreamASynch.IsConnected)
{
namedPipeStreamASynch.BeginWaitForConnection(
(resultAsynch) =>
{
string MessageToSendLocal = MessageToSend;
try
{
namedPipeStreamASynch.EndWaitForConnection(resultAsynch);
WriteMessageToPipe(MessageToSendLocal);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}, null);
}
else
{
WriteMessageToPipe(MessageToSend);
}
}
The above code is basically the code of handler of click event of a button. It is using the Pipe Stream Server declared at the level of instance. If the server is not already connected, it would wait for the connection on a ThreadPool thread using BeginWaitForConnection method. As soon as it is connected, it would executed the lambda callback, ultimately resulting in pushing data to the pipe.
The above code was about waiting for the connection on a asynchronous fashion. We can also write on an asycnh way using BeginWrite functionality available with PipeStream. For achieving the same effect, look at this definition of WriteMessageToPipe method used in above code.
private void WriteMessageToPipe(string MessageToSend)
{
//Get bytes array of the message
byte[] messageBytes = Encoding.UTF8.GetBytes(MessageToSend);
//writing asynch way
namedPipeStreamASynch.BeginWrite(messageBytes, 0, messageBytes.Length,
(resultAsynchWrite) =>
{
try
{
//just to make sure there are no exceptions
namedPipeStreamASynch.EndWrite(resultAsynchWrite);
//namedPipeStreamASynch.Disconnect();
namedPipeStreamASynch.Flush();
//namedPipeStreamASynch.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}, null);
}
This would be invoking the Write operation on a separate ThreadPool thread. As the Write operation completes, the callback would execute. In the above code, we have just flushed the contents of the PipeStream. We can also close / disconnect the stream if this is the only communication required between the client and server.
It is great to learn that PipeStream's Read operation can also be asynchronous. It supports BeginRead / EndRead. Let's assume a PipeStream client invoking the asynchronous read operation.
private void btnASynchronousRead_Click(object sender, RoutedEventArgs e)
{
//if not already connected, get connected and set message mode
if (!namedPipeClientStream.IsConnected)
{
//still synchronous
namedPipeClientStream.Connect();
//Set message mode
namedPipeClientStream.ReadMode = PipeTransmissionMode.Message;
}
//setting any arbitrary size for message. It should be defined for the protocol and documented for developers.
byte[] buffer = new byte[1024];
//asynchronous read operation
namedPipeClientStream.BeginRead(buffer, 0, 1024,
(asynchResult) =>
{
try
{
//Just to make sure that there are no exceptions during Read operation
namedPipeClientStream.EndRead(asynchResult);
//Get the string from the message buffer
string message = Encoding.UTF8.GetString(buffer);
//Get rid of unnnecessary appending characters
message = message.Substring(0, message.IndexOf("\0"));
//Invoking the Dispatcher.Invoke as we are on ThreadPool thread
Dispatcher.BeginInvoke(
new Action(
() =>
{
//because of closure issues, copy the message to a local variable to the lambda expression
string messageLocal = message;
this.textBlockMessage.Text = messageLocal;
}), null);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message); //or any thing useful in case of exception
}
}, buffer); //pass the buffer as ASynchState supports Object type (TRICK)
}
Here the definition of PipeStreamClient used above as follows:
NamedPipeClientStream namedPipeClientStream = new NamedPipeClientStream(".", "namedPipe", PipeDirection.InOut);
It must have a Pipe Stream Server something like this:
NamedPipeServerStream namedPipeStreamASynch =
new NamedPipeServerStream("namedPipe", PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous);
If we don't open the PipeStream server connection with Asynchronous mode then the execution of callback would result in exception.
I want to take a moment here to direct you to one of the post of the special exception model of Asynchronous delegates.
http://shujaatsiddiqi.blogspot.com/2010/12/asynchronous-delegate-exception-model.html
This code is still not purely asynchronous. Here Write operation on the PipeStream is still a blocking call. It can block the UI thread forever. Let's update the code to use asynchronous version of Write method (BeginWrite).
private void btnSendAsynchMessage_Click(object sender, RoutedEventArgs e)
{
string MessageToSend = this.textBox1.Text;
namedPipeStreamASynch.BeginWaitForConnection(
(resultAsynch) =>
{
try
{
string MessageToSendLocal = MessageToSend;
namedPipeStreamASynch.EndWaitForConnection(resultAsynch);
byte[] messageBytes = Encoding.UTF8.GetBytes(MessageToSendLocal);
//namedPipeStreamASynch.Write(messageBytes, 0, messageBytes.Length);
namedPipeStreamASynch.BeginWrite(messageBytes, 0, messageBytes.Length,
(resultAsynchWrite) =>
{
try
{
namedPipeStreamASynch.EndWrite(resultAsynchWrite);
namedPipeStreamASynch.Disconnect();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}, null);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}, null);
}
The above code would be using a ThreadPool thread to wait for the availability of a PipeStream Client for connection. As soon as a client becomes available, it creates another new ThreadPool thread for writing the contents of the TextBox to the pipe. May be you don't want to create so many threads for a simple write operation. In that case just create one ThreadPool thread for waiting for the connection. To write the contents to the Pipe, we can use the same thread.
Basically, writing the contents to pipe is not a feature of just server. It is rather defined in PipeStream class. This is inherited by all 4 client and server classes for PipeStream. We can introduce the similar use of Write (BeginWrite) for client.
Security of Transmission:
In order to apply security to a Pipe, we can create an instance of PipeSecurity and pass it to the SetAccessControl method of PipeStream. This would apply the access control entries in the PipeStream object to the pipe.
Data is an organization asset. The same data is shared across different processes using messages communicated through PipeStrream. If we want to make data secure, we need to apply access control entries to the PipeStream object. This would ensure the authorized use of the PipeStream.
Access to the Path is denied:
There might be many possible issues with setting up the connection between client(s) and server. It is annoying that most of the issues result in "Access to the Path is denied" message. I think had there been specialized messages for each issue it would have been a lot easier to trace the cause of the issue.
Download:
2 comments:
Unfortunately, I cannot download the example
Thank you very much, I was having trouble finding a simple explanation and this helped a lot.
Post a Comment