2012年9月24日 星期一

System.IO之使用管道在進程間通信 (System.IO.Pipes使用)


管道的用途是在同一台機器上的進程之間通信,也可以在同一網絡不同機器間通信。在.Net中可以使用匿名管道和命名管道。管道相關的類在System.IO.Pipes命名空間中。.Net中管道的本質是對windows API中管道相關函數的封裝。
使用匿名管道在父子進程之間通信:
匿名管道是一種半雙工通信,所謂的半雙工通信是指通信的兩端只有一端可寫另一端可讀;匿名管道只能在同一台機器上使用,不能在不同機器上跨網絡使用。
匿名管道顧名思義就是沒有命名的管道,它常用於父子進程之間的通信,父進程在創建子進程是要將匿名管道的句柄作為字符串傳遞給子進程,看下例子:
父進程創建了一個AnonymousPipeServerStream,然後啟動子進程,並將創建的AnonymousPipeServerStream的句柄作為參數傳遞給子進程。如下代碼:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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實例,並從此實例中讀取內容,如下代碼:
?
1
2
3
4
5
6
7
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端可以讀取任意長度的消息,而無須知道消息的長度。
如下服務端進程代碼:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
        }
    }
}
如下客戶端進程代碼:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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方法以讀取到回車換行符作為一個消息傳遞結束的標誌;而在使用基於消息傳輸時就不必這麼做了。如下示例:
服務端代碼:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
 
    }
}
客戶端代碼:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 根據我自己看法對原文做了一些改動。

沒有留言:

張貼留言