.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

Martin
03/18/2010 - 04:21

I think, that you have to set rights to a server:

PipeSecurity ps = new PipeSecurity();
PipeAccessRule par = new PipeAccessRule("Everyone",  PipeAccessRights.ReadWrite,                              System.Security.AccessControl.AccessControlType.Allow);
                ps.AddAccessRule(par);

server = new NamedPipeServerStream(PIPE_NAME, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 4096, 4096, ps);

This works for me just fine under Windows 7

reply

Bob
07/20/2010 - 11:45

On the money, Martin...

Thanks for the example...solved my problem.

reply

nij
06/18/2011 - 06:09

I'm trying this; and setting the PipeAccessRule appears to function the first time I create a NamedPipeServerStream, but when I try and open the second I get some unusual behaviour, but the summary is that code that worked does not do so any more.

Could it be that I am only setting Access Rules on the server side, and that having dome that, I _have_ to set something when the clients connect?

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

Anonymous
03/18/2010 - 06:08

Hi,

How do you do multiple NamedPipeClientStream Connection to One NamedPipeServerStream ?

The thing is, I'm developping a multi-Process application, with a MainProcessManaging Application that is monitoring the other Process States & Use. To communicate between them, and as they all are local Processes, I choose NamedPipes as the most optimized solution.

My problem today is that the MaxInstance parameter you can pass to the NamedPipeServerStream doesn't seem to do the work I was hoping for, that is accept multiple Client connection to my ServerStream.

Does anyone know how I can do the trick, or do I have to create for each of my processes a particular {Server,Client}Stream connection ?

reply

Iftikhar Ali
11/12/2010 - 11:14

Initialize your Pipe Server object like this..!

NamedPipeServerStream pipeServer = new NamedPipeServerStream("MessagingServer", PipeDirection.InOut,3);
//where 3 is the number of max instances.

At this point You can Launch your application Server application upto 3 times listening to the same named pipe. However, In the real world scenario, You would have rather have 3 threads within Your Application each creating a Pipe Server object.

Once You have serviced the client call, You can return the object back to the pool and do "waitforConnection" again.

I will post the demo of the same, soon:)..!

reply

Iftikhar Ali
11/12/2010 - 11:39

Okay here is the example..It is not very robust such i'm not thread pools or AutoResetEvent etc..To Test them You should have 2 console apps. One for Server and One for Client.

//IPC Server
class Program
    {
        public const int MAX_CONNECTIONS = 5;

        static void Main(string[] args)
        {
            Thread[] threads = new Thread[MAX_CONNECTIONS];

            AutoResetEvent evt = new AutoResetEvent(true);
           
            for (int i=0;i<threads.Count(); i++)
            {
                threads[i] = new Thread(new ThreadStart(StartServer));
                threads[i].Start();
            }
           
        }

        static void StartServer()
        {
            Server svr = new Server();
            svr.StartService();//will return when client    
                               //disconnects.
           
        }
    }

//Server.cs
 class Server:IDisposable
    {
        NamedPipeServerStream pipeServer = new NamedPipeServerStream("MessagingServer", PipeDirection.InOut, Program.MAX_CONNECTIONS);
       

        public void StartService()
        {
            System.Console.WriteLine("Server Started");

            pipeServer.WaitForConnection();


            System.Console.WriteLine("Client Connected");

           
            StreamWriter sw = new StreamWriter(pipeServer);
            StreamReader sr = new StreamReader(pipeServer);
           
            sw.AutoFlush = true;
           
            string sMsg = "";
            try
            {
                while (sMsg != "bye")
                {
                    sMsg = sr.ReadLine();
                    Console.WriteLine("Client Message: " + sMsg);
                    sw.WriteLine("Server Echo " + sMsg);
                   
                }

            }
            catch (Exception ex)
            {
                System.Console.WriteLine("Communication Error " + ex.Message);
            }
            finally
            {
                pipeServer.Close();
            }

        }


        #region IDisposable Members

        void IDisposable.Dispose()
        {
            pipeServer.Close();
        }

        #endregion
    }

//Client.cs
class Program
    {
        static void Main(string[] args)
        {

            NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "MessagingServer", PipeDirection.InOut);

            pipeClient.Connect();

            System.IO.StreamWriter sw = new StreamWriter(pipeClient);
            System.IO.StreamReader sr = new StreamReader(pipeClient);
            sw.AutoFlush = true;//otherwise, writer will not write to the underlying stream intime.
            string sMsg = "";
            Console.WriteLine(pipeClient.NumberOfServerInstances);
            try
            {
                while (sMsg != "Bye")
                {
                    Console.WriteLine("Enter command for the Server");
                    sMsg = Console.ReadLine();
                    sw.WriteLine(sMsg);
                   
                    Console.WriteLine("Server Response: " + sr.ReadLine());
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception occurred: " + ex.Message);

            }

            finally
            {
                pipeClient.Close();
            }

        }
    }

reply

belgaard
01/21/2011 - 06:48

I would like to restrict access to a named pipe, so that it can only be used within a Windows logon session (on the same machine).

In C++ I can use the logon SID on the DACL for the pipe but how can that be done in C#?

I realize that this is an old thread but I hope someone is still listening.

Regards,
Brian

reply

belgaard
01/21/2011 - 10:03

PS: Here is an MSDN reference that is useful for non-managed named pipes: http://msdn.microsoft.com/en-us/library/aa365600(v=vs.85).aspx

reply

matthieu
01/24/2011 - 10:01

using a named pipe between a service and an application seems to give "Access denied" when client calls CallNamedPipe, ON WINDOWS 7 (was working fine on XP at least)

it seems that even if I set (in the service properties) the same logon account as the one where the client application is started, I have this error code 5 "Access Denied".

any idea ??
thx

reply

Tiberius
12/15/2011 - 12:47

Hello, I'm trying to write a thin shim in C# that exposes a single named pipe (only one client at a time) but that client is written in Win32 unmanaged C++. I'm starting to suspect that pipes created with the .NET 3.5 classes aren't visible to processes that don't contain a CLR in them? The reason I say this is the Win32 client doesn't ever find the pipe no matter how many different variations I decorate the name with (e.g. "PIPENAME", "\\PIPENAME", "\\\\.\\PIPENAME", etc.). When I fire up the Winternals SysObj utility, I can't find the pipe listed anywhere. What am I missing? Thank you.

reply

Harald
02/08/2012 - 08:10

the named pipe is mapped in filesystem to "\\\\.\\pipe\\pipename"

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.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.