管道
管道提供了一种进程间通信的方法。 有两种类型的管道:
-
匿名管道。
匿名管道提供本地计算机上的进程间通信。 匿名管道需要的系统开销比命名管道少,但它提供的服务也很有限。 匿名管道为单向的,不能在网络上使用。 它们只支持单一服务器实例。 匿名管道对线程间通信或是父子进程之间的通信非常有用,对于后者,管道句柄可以轻松地传递给子进程(在子进程创建之时)。
在 .NET Framework 中,可通过使用 AnonymousPipeServerStream 和 AnonymousPipeClientStream 类来实现匿名管道。
-
命名管道。
命名管道提供一个管道服务器与一个或多个管道客户端之间的进程间通信。 命名管道可以是单向的,也可以是双向的。 它们支持基于消息的通信,允许多个客户端使用相同的管道名称同时连接服务器进程。 命名管道还支持模拟,这使得连接进程可以在远程服务器上使用它们自己的权限。
在 .NET Framework 中,可通过使用 NamedPipeServerStream 和 NamedPipeClientStream 类来实现命名管道。
如何:使用匿名管道在本地进程之间进行通信
匿名管道提供的功能比命名管道少,但它需要的系统开销也少。 您可以使用匿名管道更加轻松地在本地计算机上进行进程间通信。 不能使用匿名管道通过网络进行通信。
示例
下面的示例演示使用匿名管道将字符串从父进程发送到子进程的方式。 此示例使用 Out 的 PipeDirection 值在父进程中创建一个 AnonymousPipeServerStream 对象。 然后,父进程通过使用客户端句柄创建一个 AnonymousPipeClientStream 对象来创建一个子进程。 该子进程的 In 值为 PipeDirection。
然后,父进程将用户提供的字符串发送给子进程。 该字符串将显示在子进程中的控制台上。
下面的示例演示服务器进程。
using System; using System.IO; using System.IO.Pipes; using System.Diagnostics; class PipeServer { static void Main() { Process pipeClient = new Process(); pipeClient.StartInfo.FileName = "pipeClient.exe"; using (AnonymousPipeServerStream pipeServer = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable)) { // Show that anonymous pipes do not support Message mode. try { Console.WriteLine("[SERVER] Setting ReadMode to \"Message\"."); pipeServer.ReadMode = PipeTransmissionMode.Message; } catch (NotSupportedException e) { Console.WriteLine("[SERVER] Exception:\n {0}", e.Message); } Console.WriteLine("[SERVER] Current TransmissionMode: {0}.", pipeServer.TransmissionMode); // Pass the client process a handle to the server. pipeClient.StartInfo.Arguments = pipeServer.GetClientHandleAsString(); pipeClient.StartInfo.UseShellExecute = false; pipeClient.Start(); pipeServer.DisposeLocalCopyOfClientHandle(); try { // Read user input and send that to the client process. using (StreamWriter sw = new StreamWriter(pipeServer)) { sw.AutoFlush = true; // Send a 'sync message' and wait for client to receive it. sw.WriteLine("SYNC"); pipeServer.WaitForPipeDrain(); // Send the console input to the client process. Console.Write("[SERVER] Enter text: "); sw.WriteLine(Console.ReadLine()); } } // Catch the IOException that is raised if the pipe is broken // or disconnected. catch (IOException e) { Console.WriteLine("[SERVER] Error: {0}", e.Message); } } pipeClient.WaitForExit(); pipeClient.Close(); Console.WriteLine("[SERVER] Client quit. Server terminating."); } }
下面的示例演示客户端进程。 服务器进程启动客户端进程,并为该进程提供一个客户端句柄。 应该将从客户端代码得到的可执行文件命名为 pipeClient.exe 并在运行该服务器进程之前将其复制到服务器可执行文件所在的目录中。
using System; using System.IO; using System.IO.Pipes; class PipeClient { static void Main(string[] args) { if (args.Length > 0) { using (PipeStream pipeClient = new AnonymousPipeClientStream(PipeDirection.In, args[0])) { // Show that anonymous Pipes do not support Message mode. try { Console.WriteLine("[CLIENT] Setting ReadMode to \"Message\"."); pipeClient.ReadMode = PipeTransmissionMode.Message; } catch (NotSupportedException e) { Console.WriteLine("[CLIENT] Execption:\n {0}", e.Message); } Console.WriteLine("[CLIENT] Current TransmissionMode: {0}.", pipeClient.TransmissionMode); using (StreamReader sr = new StreamReader(pipeClient)) { // Display the read text to the console string temp; // Wait for 'sync message' from the server. do { Console.WriteLine("[CLIENT] Wait for sync..."); temp = sr.ReadLine(); } while (!temp.StartsWith("SYNC")); // Read the server data and echo to the console. while ((temp = sr.ReadLine()) != null) { Console.WriteLine("[CLIENT] Echo: " + temp); } } } } Console.Write("[CLIENT] Press Enter to continue..."); Console.ReadLine(); } }
如何:使用命名管道通过网络在进程之间进行通信
命名管道提供的功能比匿名管道多。 其功能包括通过网络进行全双工通信和多个服务器实例;基于消息的通信;以及客户端模拟,这使得连接进程可在远程服务器上使用其自己的权限集。
示例
下面的示例演示如何使用 NamedPipeServerStream 类创建命名管道。 在此示例中,服务器进程创建了四个线程。 每个线程可以接受一个客户端连接。 连接的客户端进程随后向服务器提供一个文件名。 如果客户端具有足够的权限,服务器进程就会打开文件并将其内容发送回客户端。
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); } }
下面的示例演示使用 NamedPipeClientStream 类的客户端进程。 客户端连接服务器进程并向服务器发送一个文件名。 该示例使用模拟,所以运行客户端应用程序的标识必须具有访问文件的权限。 服务器随后将文件内容发送回客户端。 文件内容随后显示在控制台上。
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; } }