.NET 3.5 Adds Named Pipes Support

Skill

.NET 3.5 Adds Named Pipes Support

Posted in:

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;
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;
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.

Kevin Saunders
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.

reply

The Reddest
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.

reply

Kevin Saunders
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.

pipeStream = new NamedPipeClientStream(".",
    "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

pipeStream = new NamedPipeClientStream(".",
    "messagepipe", PipeAccessRights.FullControl,  
    PipeOptions.None,
    Principal.TokenImpersonationLevel.Identification,
    HandleInheritability.None);
pipeStream.Connect();

are 2 ways that i have tried to set up the clientStream.

reply

Andy
07/13/2008 - 05:19

Hey Kevin,

Change the PipeDirection to InOut. That worked for me.

reply

Josh
08/26/2009 - 12:19

Can someone tell me how to pass a string from the client and read it on the server?
Thanks

reply

The Reddest
08/26/2009 - 15:27

You should be able to do it pretty much like how the server does in the above example.

StreamWriter writer = new StreamWriter(pipeClient);
writer.WriteLine("My String");

reply

AljoĊĦa
09/24/2009 - 06:26

Thank you!

reply

mike
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

reply

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

reply

Patrick
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.

reply

Anonymous
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.

reply

Anonymous
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...

reply

Anonymous
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.

reply

Rao
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());
}

reply

Anonymous
02/21/2010 - 15:37

Have you tried to Flush() the stream after sending the text? The stream writer may be buffering the data.

reply

Anonymous
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.

reply

Add Comment

Put code snippets inside language tags:
[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.

Sponsors