Named pipes offer more functionality than anonymous pipes. This functionality includes full duplex communication over a network and multiple server instances; message-based communication; and client impersonation, which enables connecting processes to use their own set of permissions on remote servers.
The following example demonstrates how to create a named pipe by using the NamedPipeClientStream class. In this example, the server process creates four threads. Each thread can accept a client connection. The connected client process then supplies the server with a file name. If the client has sufficient permissions, the server process opens the file and sends its contents back to the client.
using System; using System.IO; using System.IO.Pipes; using System.Text; using System.Threading; public class PipeServer { private static int numThreads = 4; public static void Main() { int i; Thread[] servers = new Thread[numThreads]; Console.WriteLine("\n*** Named pipe server stream with impersonation example ***\n"); Console.WriteLine("Waiting for client connect...\n"); for (i = 0; i < numThreads; i++) { servers[i] = new Thread(ServerThread); servers[i].Start(); } Thread.Sleep(250); while (i > 0) { for (int j = 0; j < numThreads; j++) { if (servers[j] != null) { if (servers[j].Join(250)) { Console.WriteLine("Server thread[{0}] finished.", servers[j].ManagedThreadId); servers[j] = null; i--; // decrement the thread watch count } } } } Console.WriteLine("\nServer threads exhausted, exiting."); } private static void ServerThread(object data) { NamedPipeServerStream pipeServer = new NamedPipeServerStream("testpipe", PipeDirection.InOut, numThreads); int threadId = Thread.CurrentThread.ManagedThreadId; // Wait for a client to connect pipeServer.WaitForConnection(); Console.WriteLine("Client connected on thread[{0}].", threadId); try { // Read the request from the client. Once the client has // written to the pipe its security token will be available. StreamString ss = new StreamString(pipeServer); // Verify our identity to the connected client using a // string that the client anticipates. ss.WriteString("I am the one true server!"); string filename = ss.ReadString(); // Read in the contents of the file while impersonating the client. ReadFileToStream fileReader = new ReadFileToStream(ss, filename); // Display the name of the user we are impersonating. Console.WriteLine("Reading file: {0} on thread[{1}] as user: {2}.", filename, threadId, pipeServer.GetImpersonationUserName()); pipeServer.RunAsClient(fileReader.Start); } // Catch the IOException that is raised if the pipe is broken // or disconnected. catch (IOException e) { Console.WriteLine("ERROR: {0}", e.Message); } pipeServer.Close(); } } // Defines the data protocol for reading and writing strings on our stream public class StreamString { private Stream ioStream; private UnicodeEncoding streamEncoding; public StreamString(Stream ioStream) { this.ioStream = ioStream; streamEncoding = new UnicodeEncoding(); } public string ReadString() { int len = 0; len = ioStream.ReadByte() * 256; len += ioStream.ReadByte(); byte[] inBuffer = new byte[len]; ioStream.Read(inBuffer, 0, len); return streamEncoding.GetString(inBuffer); } public int WriteString(string outString) { byte[] outBuffer = streamEncoding.GetBytes(outString); int len = outBuffer.Length; if (len > UInt16.MaxValue) { len = (int)UInt16.MaxValue; } ioStream.WriteByte((byte)(len / 256)); ioStream.WriteByte((byte)(len & 255)); ioStream.Write(outBuffer, 0, len); ioStream.Flush(); return outBuffer.Length + 2; } } // Contains the method executed in the context of the impersonated user public class ReadFileToStream { private string fn; private StreamString ss; public ReadFileToStream(StreamString str, string filename) { fn = filename; ss = str; } public void Start() { string contents = File.ReadAllText(fn); ss.WriteString(contents); } }
The following example shows the client process, which uses the NamedPipeClientStream class. The client connects to the server process and sends a file name to the server. The server then sends the contents of the file back to the client. The file contents are then displayed to the console.
using System; using System.IO; using System.IO.Pipes; using System.Text; using System.Security.Principal; using System.Diagnostics; using System.Threading; public class PipeClient { private static int numClients = 4; public static void Main(string[] Args) { if (Args.Length > 0) { if (Args[0] == "spawnclient") { NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "testpipe", PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation); Console.WriteLine("Connecting to server...\n"); pipeClient.Connect(); StreamString ss = new StreamString(pipeClient); // Validate the server's signature string if (ss.ReadString() == "I am the one true server!") { // The client security token is sent with the first write. // Send the name of the file whose contents are returned // by the server. ss.WriteString("c:\\textfile.txt"); // Print the file to the screen. Console.Write(ss.ReadString()); } else { Console.WriteLine("Server could not be verified."); } pipeClient.Close(); // Give the client process some time to display results before exiting. Thread.Sleep(4000); } } else { Console.WriteLine("\n*** Named pipe client stream with impersonation example ***\n"); StartClients(); } } // Helper function to create pipe client processes private static void StartClients() { int i; string currentProcessName = Environment.CommandLine; Process[] plist = new Process[numClients]; Console.WriteLine("Spawning client processes...\n"); if (currentProcessName.Contains(Environment.CurrentDirectory)) { currentProcessName = currentProcessName.Replace(Environment.CurrentDirectory, String.Empty); } // Remove extra characters when launched from Visual Studio currentProcessName = currentProcessName.Replace("\\", String.Empty); currentProcessName = currentProcessName.Replace("\"", String.Empty); for (i = 0; i < numClients; i++) { // Start 'this' program but spawn a named pipe client. plist[i] = Process.Start(currentProcessName, "spawnclient"); } while (i > 0) { for (int j = 0; j < numClients; j++) { if (plist[j] != null) { if (plist[j].HasExited) { Console.WriteLine("Client process[{0}] has exited.", plist[j].Id); plist[j] = null; i--; // decrement the process watch count } else { Thread.Sleep(250); } } } } Console.WriteLine("\nClient processes finished, exiting."); } } // Defines the data protocol for reading and writing strings on our stream public class StreamString { private Stream ioStream; private UnicodeEncoding streamEncoding; public StreamString(Stream ioStream) { this.ioStream = ioStream; streamEncoding = new UnicodeEncoding(); } public string ReadString() { int len; len = ioStream.ReadByte() * 256; len += ioStream.ReadByte(); byte[] inBuffer = new byte[len]; ioStream.Read(inBuffer, 0, len); return streamEncoding.GetString(inBuffer); } public int WriteString(string outString) { byte[] outBuffer = streamEncoding.GetBytes(outString); int len = outBuffer.Length; if (len > UInt16.MaxValue) { len = (int)UInt16.MaxValue; } ioStream.WriteByte((byte)(len / 256)); ioStream.WriteByte((byte)(len & 255)); ioStream.Write(outBuffer, 0, len); ioStream.Flush(); return outBuffer.Length + 2; } }
The client and server processes in this example are intended to run on the same computer, so the server name provided to the NamedPipeClientStream object is "localhost". If the client and server processes were on separate computers, "localhost" would be replaced with the network name of the computer that runs the server process.