System.IO之使用管道在进程间通信 (System.IO.Pipes使用)
管道的用途是在同一台机器上的进程之间通信,也可以在同一网络不同机器间通信。在.Net中可以使用匿名管道和命名管道。管道相关的类在System.IO.Pipes命名空间中。.Net中管道的本质是对windows API中管道相关函数的封装。
使用匿名管道在父子进程之间通信:
匿名管道是一种半双工通信,所谓的半双工通信是指通信的两端只有一端可写另一端可读;匿名管道只能在同一台机器上使用,不能在不同机器上跨网络使用。
匿名管道顾名思义就是没有命名的管道,它常用于父子进程之间的通信,父进程在创建子进程是要将匿名管道的句柄作为字符串传递给子进程,看下例子:
父进程创建了一个AnonymousPipeServerStream,然后启动子进程,并将创建的AnonymousPipeServerStream的句柄作为参数传递给子进程。如下代码:
Process process = new Process(); process.StartInfo.FileName = "child.exe"; //创建匿名管道流实例, using (AnonymousPipeServerStream pipeStream = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable)) { //将句柄传递给子进程 process.StartInfo.Arguments = pipeStream.GetClientHandleAsString(); process.StartInfo.UseShellExecute = false; process.Start(); //销毁子进程的客户端句柄? pipeStream.DisposeLocalCopyOfClientHandle(); using (StreamWriter sw = new StreamWriter(pipeStream)) { sw.AutoFlush = true; //向匿名管道中写入内容 sw.WriteLine(Console.ReadLine()); } } process.WaitForExit(); process.Close();
子进程声明了一个AnonymousPipeClientStream实例,并从此实例中读取内容,如下代码:
using (StreamReader sr = new StreamReader( new AnonymousPipeClientStream(PipeDirection.In, args[0]))) { string line; while ((line = sr.ReadLine()) != null) { Console.WriteLine("Echo: {0}", line); } }
这个程序要在cmd命令行中执行,否则看不到执行效果,执行的结果是在父进程中输入一行文本,子进程输出Echo:文本。
命名管道:
命名管道的功能比匿名管道更强大,可以在进程之间做双工通信(即通信的两个进程都是既可以读也可写的);命名管道也可以实现跨网络在不同机器之间进行通信。可以在多线程中创建多个NamedPipeServerStream实例,为多个client端服务。另外命名管道还支持消息传输,这样client端可以读取任意长度的消息,而无须知道消息的长度。
如下服务端进程代码:
using (NamedPipeServerStream pipeStream = new NamedPipeServerStream("testpipe")) { pipeStream.WaitForConnection(); using (StreamWriter writer = new StreamWriter(pipeStream)) { writer.AutoFlush = true; string temp; while ((temp = Console.ReadLine()) != "stop") { writer.WriteLine(temp); } } }
如下客户端进程代码:
static void Main(string[] args) { using (NamedPipeClientStream pipeStream = new NamedPipeClientStream("testpipe")) { pipeStream.Connect(); //在client读取server端写的数据 using (StreamReader rdr = new StreamReader(pipeStream)) { string temp; while ((temp = rdr.ReadLine()) != "stop") { Console.WriteLine("{0}:{1}",DateTime.Now,temp); } } } Console.Read(); }
上面代码执行结果是服务端输入文本,客户端显示接受到文本的时间和文本内容。上面说了命名管道是全双工通信的,所以你也可以让客户端写内容,而服务端接受内容,代码大同小异。
基于消息传输的命名管道:
基于消息的命名管道可以传递不定长的内容,而无需传递内容长度或者结束符,上面非基于消息的传输我们都是在向管道中输入一段文本,使用WriteLine方法以回车换行作为结束符传输信息,而管道的另一端再使用ReadLine方法以读取到回车换行符作为一个消息传递结束的标志;而在使用基于消息传输时就不必这么做了。如下示例:
服务端代码:
static void Main(string[] args) { UTF8Encoding encoding = new UTF8Encoding(); string message1 = "Named Pipe Message Example."; string message2 = "Another Named Pipe Message Example."; Byte[] bytes; using (NamedPipeServerStream pipeStream = new NamedPipeServerStream("messagepipe", PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.None)) { pipeStream.WaitForConnection(); // Let’s send two messages. bytes = encoding.GetBytes(message1); pipeStream.Write(bytes, 0, bytes.Length); bytes = encoding.GetBytes(message2); pipeStream.Write(bytes, 0, bytes.Length); } }
客户端代码:
static void Main(string[] args) { Decoder decoder = Encoding.UTF8.GetDecoder(); Byte[] bytes = new Byte[10]; Char[] chars = new Char[10]; using (NamedPipeClientStream pipeStream = new NamedPipeClientStream("messagepipe")) { pipeStream.Connect(); pipeStream.ReadMode = PipeTransmissionMode.Message; int numBytes; do { string message = ""; do { numBytes = pipeStream.Read(bytes, 0, bytes.Length); int numChars = decoder.GetChars(bytes, 0, numBytes, chars, 0); message += new String(chars, 0, numChars); } while (!pipeStream.IsMessageComplete); decoder.Reset(); Console.WriteLine(message); } while (numBytes != 0); } }
基于消息传递信息需要在构造NamedPipeServerStream时指定PipeTransmissionMode为PipeTransmissionMode.Message;而在客户端接收时也需指定NamedPipeClientStream的ReadMode属性为PipeTransmissionMode.Message
以上只是管道使用的简单实例,真正使用时还需要考虑多线程访问控制,权限控制等。
本文内容翻译自:http://blogs.msdn.com/b/bclteam/archive/2006/12/07/introducing-pipes-justin-van-patten.aspx 根据我自己看法对原文做了一些改动。
相关随笔:
.Net那点事儿系列:System.IO之windows文件操作
.Net那点事儿系列:System.IO之Stream
System.IO之内存映射文件共享内存
System.IO之使用管道在进程间通信 (System.IO.Pipes使用)
System.IO系列:局域网内多线程使用命名管道在进程之间通信实例