Leave it to Microsoft to make all my hard work worthless. A while ago, I posted a tutorial on how to use named pipes in C# and .NET. Back then it took a lot of hard work and a lot of Windows API calls to get named pipes integrated into a .NET 2.0 application. Now, thanks to .NET 3.5, named pipes are as easy as importing System.IO.Pipes.
If you want a named pipe server, all you have to do is create some instances of NamedPipeServerStream to handle each client connection. I stole the following code straight from the MSDN documentation.
using System.IO;
using System.IO.Pipes;
class PipeServer
{
static void Main()
{
using (NamedPipeServerStream pipeServer =
new NamedPipeServerStream("testpipe", PipeDirection.Out))
{
Console.WriteLine("NamedPipeServerStream object created.");
// Wait for a client to connect
Console.Write("Waiting for client connection...");
pipeServer.WaitForConnection();
Console.WriteLine("Client connected.");
try
{
// Read user input and send that to the client process.
using (StreamWriter sw = new StreamWriter(pipeServer))
{
sw.AutoFlush = true;
Console.Write("Enter text: ");
sw.WriteLine(Console.ReadLine());
}
}
// Catch the IOException that is raised if the pipe is
// broken or disconnected.
catch (IOException e)
{
Console.WriteLine("ERROR: {0}", e.Message);
}
}
}
}
Just like most .NET streams, the NamedPipeServerStream supports both synchronous and asynchronous communication. The stream is also full duplex, meaning it can be read and written to at the same time. Getting the overlapped IO working was one of the biggest challenges to overcome for named pipes in previous versions of .NET.
Making a client is just as easy as the server. The Pipes namespace has another stream, called NamedPipeClientStream, which does all the work for you.
using System.IO;
using System.IO.Pipes;
class PipeClient
{
static void Main(string[] args)
{
using (NamedPipeClientStream pipeClient =
new NamedPipeClientStream(".", "testpipe", PipeDirection.In))
{
// Connect to the pipe or wait until the pipe is available.
Console.Write("Attempting to connect to pipe...");
pipeClient.Connect();
Console.WriteLine("Connected to pipe.");
Console.WriteLine("There are currently {0} pipe server instances open.",
pipeClient.NumberOfServerInstances);
using (StreamReader sr = new StreamReader(pipeClient))
{
// Display the read text to the console
string temp;
while ((temp = sr.ReadLine()) != null)
{
Console.WriteLine("Received from server: {0}", temp);
}
}
}
Console.Write("Press Enter to continue...");
Console.ReadLine();
}
}
When I get a chance to use these objects a little more, I'll post a more in-depth tutorial. Personally, I'd like to see a set of objects similar to the TcpClient and TcpServer classes for handling named pipes, but I guess I'll have to wait a little longer for that.
05/16/2008 - 09:14
Thank you for the great example of using managed named pipes with .net 3.5. However I have been runnin into an issue that i have not been able to find any help on.
What i am trying to do is (in Vista) run a windows service that has a named piped server and an application that runs under limited permissions as a local user. When the local user app tries to connect to the server an error is thrown that 'Access to the path is denied'. If the app is running under an administrator account it works perfectly. I need my app to be able to communicate with the service. I know there are other possible solutions but i felt that named pipes would be the most secure. Please any help is appreciated.
05/16/2008 - 10:11
It looks like all you need to do is set up some permissions on the named pipe. You can check out the SetAccessControl function for the NamedPipeServerStream to see how to do this. This function allows you to grant specific permissions for users or groups. I don't have much experience with named pipe permissions. Hopefully that helps.
05/16/2008 - 10:30
yeah, that is what is what i have been trying to figure out for a day or two now. I haven't been able to find good information on how to setup the rights needed to connect to a named pipe in a different user space (as the service is running as local system). In my client i tried setting the PipeAccessRights to 'full control' as well as a few others with no success. But maybe i just don't know how to setup the options correctly.
Thank you for your quick reply.
"messagepipe", PipeDirection.Out, PipeOptions.None,
Principal.TokenImpersonationLevel.Identification,
HandleInheritability.None);
PipeSecurity ps = pipeStream.GetAccessControl();
PipeAccessRule par = new PipeAccessRule("localuser",
PipeAccessRights.FullControl,
AccessControl.AccessControlType.Allow);
ps.AddAccessRule(par);
pipeStream.SetAccessControl(ps);
pipeStream.Connect();
and
"messagepipe", PipeAccessRights.FullControl,
PipeOptions.None,
Principal.TokenImpersonationLevel.Identification,
HandleInheritability.None);
pipeStream.Connect();
are 2 ways that i have tried to set up the clientStream.
07/13/2008 - 05:19
Hey Kevin,
Change the PipeDirection to InOut. That worked for me.
08/26/2009 - 12:19
Can someone tell me how to pass a string from the client and read it on the server?
Thanks
08/26/2009 - 15:27
You should be able to do it pretty much like how the server does in the above example.
writer.WriteLine("My String");
09/24/2009 - 06:26
Thank you!
10/09/2009 - 12:55
Hello,
looks like i have the exact same problem. I hava a PipeServer running on one machine and a PipeClient on another. It is working fine as long as I am running both apps under the same account (Username/Passwort) But if I change the Clients User, I get the "Wrong Username or Password Error" when I try to connect the Client. I just have not figured out, how to pass Userinformations to the Pipe. I am not even sure if I have to set them on the Server or Client. Adding a PipeAccessRule had no effect on the Client Side. On the Server Side i got an Error, that the User i tried to add was unknown (logical, the User does not exist on the Server machine)
My Server:
_PipeInServer = New NamedPipeServerStream(_PipeNameIn, PipeDirection.InOut)
My Client:
_PipeOutClient = New NamedPipeClientStream(_ServerName, _PipeNameIn, PipeDirection.InOut)
You have any Idea? Thank you
mike
10/11/2009 - 16:33
ok, never mind
I didn't figure out how to get the pipes work properly via network, so I added a Net.Sockets Object for that. Works great now. If anyone else has the same Problem I suggest to think twice if you really need the Pipes over Network. Locally I think they are great, but for remote.... naeh.
greetings
mike
01/27/2010 - 19:09
Is there anyway to use this to transfer serializable objects through?
also what about having multiple clients? the given example only works with 1:1.
02/21/2010 - 15:49
The way I transfer XML serialized objects is to first serialize the object to a memory stream (using UTF8 encoding). The MemoryStream.ToArray() method provides you with the data you need to send. Using a BinaryWriter over the Pipe Stream (or NetworkStream) send an integer indicating the length of the byte array you are about send. Then send the byte array. Remember to Flush() the stream so that nothing is left in the BinaryWriter buffer.
On the receiving end, first read the 4 bytes that indicate the length of the serialized object (perform a sanity check as well, such as ensuring that the value is >= 1 and <= SomeLargeNumber). Then read that number of bytes from the stream (use a timeout as well just in case the connection is closed). Finally, you can wrap the received byte[] in a MemoryStream and deserialize it.
XML serialization works well in this case as it is a lot more forgiving than binary serialization which includes assembly name and version number. Of course, this method will still work with binary serialization.
02/19/2010 - 12:14
when i send a string using the method above, the string is not received by the client untill i close the streamwriter...
02/21/2010 - 15:38
As per the below message, have you tried to Flush() the stream? I have always done this with Socket communications and it works well.
02/19/2010 - 12:19
I have two winform applications A and B. A has a text box and a button to send the text and B has a list box to collect the received string.
Now when i use the code below , the message is not received. However if i add a sw.Close() below the 'sw.WriteLine(....) function it is working only once. Next message throws a exception stating that pipe has been closed....
using (StreamWriter sw = new StreamWriter(pipeServer))
{
sw.AutoFlush = true;
Console.Write("Enter text: ");
sw.WriteLine(Console.ReadLine());
}
02/21/2010 - 15:37
Have you tried to Flush() the stream after sending the text? The stream writer may be buffering the data.
02/21/2010 - 15:40
And yes the next message will say you can't access a closed stream. The Dispose() method of the StreamWriter will be closing the underlying stream.
Add Comment
[language] [/language]
Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]
See here for supported languages.
Javascript must be enabled to submit anonymous comments - or you can login.