管道为进程间通信提供了平台。 管道分为两种类型:
- 匿名管道。
匿名管道在本地计算机上提供进程间通信。与命名管道相比,虽然匿名管道需要的开销更少,但提供的服务有限。 匿名管道是单向的,不能通过网络使用。 仅支持一个服务器实例。 匿名管道可用于线程间通信,也可用于父进程和子进程之间的通信,因为管道句柄可以轻松传递给所创建的子进程。
可通过使用 AnonymousPipeServerStream 和 AnonymousPipeClientStream 类来实现匿名管道。
下面的示例展示了如何使用匿名管道将字符串从父进程发送到子进程。 此示例在父进程中创建 AnonymousPipeServerStream 对象,它的 PipeDirection 值为 Out。 然后,父进程使用客户端句柄创建 AnonymousPipeClientStream 对象,以创建子进程。 子进程的 PipeDirection 值为 In。
下面的示例展示了服务器进程:
1 using System; 2 using System.IO; 3 using System.IO.Pipes; 4 using System.Diagnostics; 5 6 class PipeServer 7 { 8 static void Main() 9 { 10 Process pipeClient = new Process(); 11 12 pipeClient.StartInfo.FileName = "pipeClient.exe"; 13 14 using (AnonymousPipeServerStream pipeServer = 15 new AnonymousPipeServerStream(PipeDirection.Out, 16 HandleInheritability.Inheritable)) 17 { 18 Console.WriteLine("[SERVER] Current TransmissionMode: {0}.", 19 pipeServer.TransmissionMode); 20 21 // Pass the client process a handle to the server. 22 pipeClient.StartInfo.Arguments = 23 pipeServer.GetClientHandleAsString(); 24 pipeClient.StartInfo.UseShellExecute = false; 25 pipeClient.Start(); 26 27 pipeServer.DisposeLocalCopyOfClientHandle(); 28 29 try 30 { 31 // Read user input and send that to the client process. 32 using (StreamWriter sw = new StreamWriter(pipeServer)) 33 { 34 sw.AutoFlush = true; 35 // Send a 'sync message' and wait for client to receive it. 36 sw.WriteLine("SYNC"); 37 pipeServer.WaitForPipeDrain(); 38 // Send the console input to the client process. 39 Console.Write("[SERVER] Enter text: "); 40 sw.WriteLine(Console.ReadLine()); 41 } 42 } 43 // Catch the IOException that is raised if the pipe is broken 44 // or disconnected. 45 catch (IOException e) 46 { 47 Console.WriteLine("[SERVER] Error: {0}", e.Message); 48 } 49 } 50 51 pipeClient.WaitForExit(); 52 pipeClient.Close(); 53 Console.WriteLine("[SERVER] Client quit. Server terminating."); 54 } 55 }
下面的示例展示了客户端进程。 服务器进程启动客户端进程,并为此进程提供客户端句柄。
1 using System; 2 using System.IO; 3 using System.IO.Pipes; 4 5 class PipeClient 6 { 7 static void Main(string[] args) 8 { 9 if (args.Length > 0) 10 { 11 using (PipeStream pipeClient = 12 new AnonymousPipeClientStream(PipeDirection.In, args[0])) 13 { 14 Console.WriteLine("[CLIENT] Current TransmissionMode: {0}.", 15 pipeClient.TransmissionMode); 16 17 using (StreamReader sr = new StreamReader(pipeClient)) 18 { 19 // Display the read text to the console 20 string temp; 21 22 // Wait for 'sync message' from the server. 23 do 24 { 25 Console.WriteLine("[CLIENT] Wait for sync..."); 26 temp = sr.ReadLine(); 27 } 28 while (!temp.StartsWith("SYNC")); 29 30 // Read the server data and echo to the console. 31 while ((temp = sr.ReadLine()) != null) 32 { 33 Console.WriteLine("[CLIENT] Echo: " + temp); 34 } 35 } 36 } 37 } 38 Console.Write("[CLIENT] Press Enter to continue..."); 39 Console.ReadLine(); 40 } 41 }
- 命名管道。
命名管道在管道服务器和一个或多个管道客户端之间提供进程间通信。 命名管道可以是单向的,也可以是双向的。 它们支持基于消息的通信,并允许多个客户端使用相同的管道名称同时连接到服务器进程。 命名管道还支持模拟,这样连接进程就可以在远程服务器上使用自己的权限。
可通过使用 NamedPipeServerStream 和 NamedPipeClientStream 类来实现命名管道。
下面的示例展示了如何使用 NamedPipeServerStream 类创建命名管道:
1 using System; 2 using System.IO; 3 using System.IO.Pipes; 4 using System.Text; 5 using System.Threading; 6 7 public class PipeServer 8 { 9 private static int numThreads = 4; 10 11 public static void Main() 12 { 13 int i; 14 Thread[] servers = new Thread[numThreads]; 15 16 Console.WriteLine(" *** Named pipe server stream with impersonation example *** "); 17 Console.WriteLine("Waiting for client connect... "); 18 for (i = 0; i < numThreads; i++) 19 { 20 servers[i] = new Thread(ServerThread); 21 servers[i].Start(); 22 } 23 Thread.Sleep(250); 24 while (i > 0) 25 { 26 for (int j = 0; j < numThreads; j++) 27 { 28 if (servers[j] != null) 29 { 30 if (servers[j].Join(250)) 31 { 32 Console.WriteLine("Server thread[{0}] finished.", servers[j].ManagedThreadId); 33 servers[j] = null; 34 i--; // decrement the thread watch count 35 } 36 } 37 } 38 } 39 Console.WriteLine(" Server threads exhausted, exiting."); 40 } 41 42 private static void ServerThread(object data) 43 { 44 NamedPipeServerStream pipeServer = 45 new NamedPipeServerStream("testpipe", PipeDirection.InOut, numThreads); 46 47 int threadId = Thread.CurrentThread.ManagedThreadId; 48 49 // Wait for a client to connect 50 pipeServer.WaitForConnection(); 51 52 Console.WriteLine("Client connected on thread[{0}].", threadId); 53 try 54 { 55 // Read the request from the client. Once the client has 56 // written to the pipe its security token will be available. 57 58 StreamString ss = new StreamString(pipeServer); 59 60 // Verify our identity to the connected client using a 61 // string that the client anticipates. 62 63 ss.WriteString("I am the one true server!"); 64 string filename = ss.ReadString(); 65 66 // Read in the contents of the file while impersonating the client. 67 ReadFileToStream fileReader = new ReadFileToStream(ss, filename); 68 69 // Display the name of the user we are impersonating. 70 Console.WriteLine("Reading file: {0} on thread[{1}] as user: {2}.", 71 filename, threadId, pipeServer.GetImpersonationUserName()); 72 pipeServer.RunAsClient(fileReader.Start); 73 } 74 // Catch the IOException that is raised if the pipe is broken 75 // or disconnected. 76 catch (IOException e) 77 { 78 Console.WriteLine("ERROR: {0}", e.Message); 79 } 80 pipeServer.Close(); 81 } 82 } 83 84 // Defines the data protocol for reading and writing strings on our stream 85 public class StreamString 86 { 87 private Stream ioStream; 88 private UnicodeEncoding streamEncoding; 89 90 public StreamString(Stream ioStream) 91 { 92 this.ioStream = ioStream; 93 streamEncoding = new UnicodeEncoding(); 94 } 95 96 public string ReadString() 97 { 98 int len = 0; 99 100 len = ioStream.ReadByte() * 256; 101 len += ioStream.ReadByte(); 102 byte[] inBuffer = new byte[len]; 103 ioStream.Read(inBuffer, 0, len); 104 105 return streamEncoding.GetString(inBuffer); 106 } 107 108 public int WriteString(string outString) 109 { 110 byte[] outBuffer = streamEncoding.GetBytes(outString); 111 int len = outBuffer.Length; 112 if (len > UInt16.MaxValue) 113 { 114 len = (int)UInt16.MaxValue; 115 } 116 ioStream.WriteByte((byte)(len / 256)); 117 ioStream.WriteByte((byte)(len & 255)); 118 ioStream.Write(outBuffer, 0, len); 119 ioStream.Flush(); 120 121 return outBuffer.Length + 2; 122 } 123 } 124 125 // Contains the method executed in the context of the impersonated user 126 public class ReadFileToStream 127 { 128 private string fn; 129 private StreamString ss; 130 131 public ReadFileToStream(StreamString str, string filename) 132 { 133 fn = filename; 134 ss = str; 135 } 136 137 public void Start() 138 { 139 string contents = File.ReadAllText(fn); 140 ss.WriteString(contents); 141 } 142 }
下面的示例展示了使用 NamedPipeClientStream 类的客户端进程。
1 using System; 2 using System.IO; 3 using System.IO.Pipes; 4 using System.Text; 5 using System.Security.Principal; 6 using System.Diagnostics; 7 using System.Threading; 8 9 public class PipeClient 10 { 11 private static int numClients = 4; 12 13 public static void Main(string[] Args) 14 { 15 if (Args.Length > 0) 16 { 17 if (Args[0] == "spawnclient") 18 { 19 NamedPipeClientStream pipeClient = 20 new NamedPipeClientStream(".", "testpipe", 21 PipeDirection.InOut, PipeOptions.None, 22 TokenImpersonationLevel.Impersonation); 23 24 Console.WriteLine("Connecting to server... "); 25 pipeClient.Connect(); 26 27 StreamString ss = new StreamString(pipeClient); 28 // Validate the server's signature string 29 if (ss.ReadString() == "I am the one true server!") 30 { 31 // The client security token is sent with the first write. 32 // Send the name of the file whose contents are returned 33 // by the server. 34 ss.WriteString("c:\textfile.txt"); 35 36 // Print the file to the screen. 37 Console.Write(ss.ReadString()); 38 } 39 else 40 { 41 Console.WriteLine("Server could not be verified."); 42 } 43 pipeClient.Close(); 44 // Give the client process some time to display results before exiting. 45 Thread.Sleep(4000); 46 } 47 } 48 else 49 { 50 Console.WriteLine(" *** Named pipe client stream with impersonation example *** "); 51 StartClients(); 52 } 53 } 54 55 // Helper function to create pipe client processes 56 private static void StartClients() 57 { 58 int i; 59 string currentProcessName = Environment.CommandLine; 60 Process[] plist = new Process[numClients]; 61 62 Console.WriteLine("Spawning client processes... "); 63 64 if (currentProcessName.Contains(Environment.CurrentDirectory)) 65 { 66 currentProcessName = currentProcessName.Replace(Environment.CurrentDirectory, String.Empty); 67 } 68 69 // Remove extra characters when launched from Visual Studio 70 currentProcessName = currentProcessName.Replace("\", String.Empty); 71 currentProcessName = currentProcessName.Replace(""", String.Empty); 72 73 for (i = 0; i < numClients; i++) 74 { 75 // Start 'this' program but spawn a named pipe client. 76 plist[i] = Process.Start(currentProcessName, "spawnclient"); 77 } 78 while (i > 0) 79 { 80 for (int j = 0; j < numClients; j++) 81 { 82 if (plist[j] != null) 83 { 84 if (plist[j].HasExited) 85 { 86 Console.WriteLine("Client process[{0}] has exited.", 87 plist[j].Id); 88 plist[j] = null; 89 i--; // decrement the process watch count 90 } 91 else 92 { 93 Thread.Sleep(250); 94 } 95 } 96 } 97 } 98 Console.WriteLine(" Client processes finished, exiting."); 99 } 100 } 101 102 // Defines the data protocol for reading and writing strings on our stream 103 public class StreamString 104 { 105 private Stream ioStream; 106 private UnicodeEncoding streamEncoding; 107 108 public StreamString(Stream ioStream) 109 { 110 this.ioStream = ioStream; 111 streamEncoding = new UnicodeEncoding(); 112 } 113 114 public string ReadString() 115 { 116 int len; 117 len = ioStream.ReadByte() * 256; 118 len += ioStream.ReadByte(); 119 byte[] inBuffer = new byte[len]; 120 ioStream.Read(inBuffer, 0, len); 121 122 return streamEncoding.GetString(inBuffer); 123 } 124 125 public int WriteString(string outString) 126 { 127 byte[] outBuffer = streamEncoding.GetBytes(outString); 128 int len = outBuffer.Length; 129 if (len > UInt16.MaxValue) 130 { 131 len = (int)UInt16.MaxValue; 132 } 133 ioStream.WriteByte((byte)(len / 256)); 134 ioStream.WriteByte((byte)(len & 255)); 135 ioStream.Write(outBuffer, 0, len); 136 ioStream.Flush(); 137 138 return outBuffer.Length + 2; 139 } 140 }
可靠编程
因为此示例中的客户端进程和服务器进程可以在同一台计算机上运行,所以提供给 NamedPipeClientStream 对象的服务器名称为 "."
。 如果客户端进程和服务器进程在不同的计算机上运行,"."
会被替换为运行服务器进程的计算机的网络名称。