天天看點

管道(Pipes)

說明:此文檔由MSDN翻譯而來

管道概述

    管道就是程序之間用來通信的一塊共享記憶體.建立管道的程序被稱為管道伺服器,其它連接配接到管道上的程序被稱為管道客戶.一個程序向管道中寫入資訊,然後其它的程序則可以從管道中讀取該資訊.下面來讨論如何建立,管理和使用管道.

有兩種類型的管道:

匿名管道

命名管道

.匿名管道的操作比命名管道簡單,但是能夠提供的服務也是有限的.

管道,這個術語本身就暗示了它是一個資訊的管道.從概念上講,一個管道有兩端.

單向管道

限制一端的程序隻能向管道中寫,而另一端的程序隻能從管道中讀.

雙向管道

則允許兩端的程序向管道中寫入或讀出.

匿名管道

匿名管道是一個沒有命名的單向管道,通常用來在父程序和子程序之間傳輸資料.匿名管道總是本地的,即它不能用來跨網絡傳輸資訊.

匿名管道操作

函數

CreatePipe

可以建立一個匿名管道并傳回兩個句柄:一個讀管道句柄和一個寫管道句柄.讀管道句柄對管道隻有讀權限,寫管道句柄對管道則隻有寫權限.為了使用管道進行交流,管道伺服器必須把其中的一個管道句柄傳遞給另外一個程序.通常這是通過繼承來完成的,即管道伺服器允許管道句柄被子程序繼承.管道伺服器還能夠使用

DuplicateHandle

函數來複制管道句柄,然後通過某種形式的程序間互動(如DDE或共享記憶體)将句柄傳遞給一個不相關的程序.

管道伺服器可以把隻讀句柄或隻寫句柄中的任何一個傳遞出去,這完全取決于管道客戶的需求.可以在

ReadFile

函數中使用管道的讀句柄來從管道中讀取資訊.

ReadFile

在下列情況下會傳回:别的程序向管道中寫入了資訊後;所有的寫管道句柄都關閉了;讀管道出錯. 

可以在

riteFile

函數中使用管道的寫句柄來向管道中寫資訊.WriteFile函數直到寫完指定數量位元組的資訊或者寫入出錯時才會傳回.如果管道已經滿了,WriteFile會等待别的程序從管道中讀出資訊,為管道騰出空間寫入剩下的資訊才傳回.管道伺服器在使用

CreatePipe

函數建立管道時會指定管道的大小.

匿名管道不支援異步讀寫操作.這意味着你不能對匿名管道使用ReadFileEx和WriteFileEx操作.而且ReadFile和WriteFile的lpOverlapped參數也是被忽略的,當其對匿名管道進行操作時.

匿名管道會一直存在,直到它的所有讀和寫句柄都關閉.一個程序可以使用

CloseHandle

函數關閉它的管道句柄.程序終止後,它的所有管道句柄會自動關閉.

從實作上講,匿名管道其實是一個有着特殊名字的命名管道.是以,許多時候你可以将匿名管道的句柄傳遞給一個需要命名管道句柄的函數.

管道句柄繼承

管道伺服器可以通過下列方式來控制它的管道句柄能夠被繼承:

·CreatePipe函數接受

SECURITY_ATTRIBUTES

結構.如果管道伺服器将SECURITY_ATTRIBUTES結構的bInheritHandle成員置為TRUE時,則CreatePipe傳回的句柄是可以被繼承的;

·管道伺服器可以使用

DuplicateHandle

函數來改變管道的繼承性.管道伺服器能夠建立一個可繼承句柄的不可繼承複本;或者不可繼承句柄的可繼承複本;

·管道伺服器可以使用

CreateProcess

函數來指定子程序繼承它的所有句柄或者不繼承它的任何句柄.

當一個子程序繼承了管道句柄之後,作業系統就會允許它通路這個管道.不過,父程序必須把這個句柄值傳遞給子程序.典型情況下,父程序會重定向标準輸出句柄到子程序,具體步驟如下:

1.父程序(管道伺服器)調用

GetStdHandle

函數擷取目前标準輸出的句柄;儲存該句柄,以便在子程序被建立後可以恢複這個原始的标準輸出句柄

2.調用

SetStdHandle

函數,将标準輸出句柄替換為管道句柄.然後父程序就可以建立子程序了.

3.調用

CloseHandle

函數關閉寫管道句柄.一旦子程序繼承了寫管道句柄之後,父程序則沒有必要儲存它的複本了.

4.調用

SetStdHandle

函數恢複原始的标準輸出句柄.

子程序使用

GetStdHandle

函數擷取标準輸出句柄,其實這時候标準輸出句柄已經被更換為管道句柄了.于是子程序就可以使用WriteFile函數向管道中發送資訊了.當子程序不再需要向管道中發送資訊時,就應當使用CloseHandle函數來關閉管道句柄.當然在子程序終止後,該句柄也會自動關閉.

父程序使用ReadFile函數從管道中接受輸入.資料作為位元組流被寫入到管道.這意味着從管道中讀資訊的父程序無法區分開兩個獨立的寫操作所寫入的資料,除非父程序和子程序都遵守某種協定以訓示一個寫操作的結束.當所有寫管道句柄都關閉了時,ReadFile函數傳回0.是以,父程序在調用ReadFile函數之前應該關閉自己的所有的寫管道句柄,這是很重要的.否則,ReadFile函數永遠不會傳回0,因為父程序還有一個打開的寫管道句柄.

重定向标準輸入句柄的步驟與重定向标準輸出是相似的,不同的是讀管道句柄用作子程序的标準輸入句柄.這時,父程序必須保證子程序沒有繼承它的寫管道句柄.否則,子程序的ReadFile操作就不會傳回0了.

附錄一是一個使用匿名管道重定向子程序标準輸入輸出的例子.

匿名管道安全和通路權限

Windows NT安全系統允許你對匿名管道的通路權限進行控制。

在使用CreatePipe函數建立管道時,你可以為管道指定一個安全描述符(security descriptor)。安全描述符既能控制着管道的讀和寫。如果你将安全描述符指定為NULL,則管道獲得一個預設的安全描述符。

調用GetSecurityInfo函數可以擷取管道的安全描述符,而SetSecurityInfo函數則可以設定安全描述符。

命名管道

一個命名管道可以有多個執行個體,所有的執行個體共享同一個名字,但是每個執行個體都有自己的緩沖區和句柄,為客戶和伺服器的交流提供各自獨立的通道。執行個體的運用使得多個管道客戶能夠同時使用相同名字的管道。

任何一個程序隻要遵守安全檢查,都能夠通路命名管道。是以,命名管道是相關或不相關程序之間進行交流的一種簡便的方式。

任何一個程序都可以充當伺服器或者客戶的角色,這使得p2p交流成為可能。

命名管道可以支援本地程序間的交流,也可以支援網絡間的程序之間的交流。一旦伺服器在運作,所有的命名管道都可以從遠端通路。

管道名稱

管道伺服器調用CreateNamedPipes函數建立一個或多個命名管道執行個體的時候必須為該管道指定名字。管道客戶在調用CreateFile或CallNamedPipe函數連接配接到一個命名管道執行個體時也需要指定管道名字。

在調用CreateFile, WaitNamedPipe或CallNamedPipe等函數時,需要指定管道名,管道名的形式如下:

       //ServerName/pip/PipeName

(其中ServerName是一個遠端主機的名字或者一個點号,點号表示本地主機。管道的名字由PipeName指定,可以包含除斜杠之外的任何字元,并且是大小寫不敏感的。)

管道伺服器不能夠為遠端主機建立一個管道,是以CreateNamedPipe必須總是使用點号來作為ServerName,如:

//./pipe/PipeName

通常,管道伺服器将管道名稱傳遞給管道客戶,以便客戶能夠連接配接到管道上。而且,客戶在編譯時候也必須知道管道名稱。

命名管道的打開模式(Open Modes)

管道伺服器可以有幾種不同的打開模式,包括access,overlap和write-through等模式。管道伺服器可以通過CreateNamedPipe函數的dwOpenMode參數指定打開模式,而管道客戶則能夠使用CreateFile函數為他們的管道句柄指定打開模式。

-Access Mode

附錄一 建立一個标準輸入輸出被重定向了的子程序

這個例子示範了控制台程序(console process)使用CreateProcess函數如何建立子程序,同時也示範了使用匿名管道重定向子程序标準輸入輸出的技術.注意命名管道也可以用來重定向程序的标準輸入輸出.

CreatePipe函數使用SECURITY_ATTRIBUTES結構體來建立讀寫句柄都可以被繼承的兩個管道.一個管道的讀句柄作為子程序的标準輸入,另一個管道的寫句柄作為子程序的标準輸出.這兩個管道句柄都在STARTUPINFO結構體中指定,該結構體使得它們成為标準句柄而被子程序所繼承.

父程序使用兩個管道的另一端,向子程序的輸入中寫資料,從子程序的輸出中讀資料.父程序擁有的兩個管道句柄也是可繼承的,不過子程序不能真的繼承它們.在建立子程序之前,父程序必須使用DuplicateHandle函數來建立程式定義的不能被繼承的全局變量hChildStdinWr的複本,然後使用CloseHandle來關閉可繼承的句柄.下面便是代碼:

#include <stdio.h> 
         
#include <windows.h>  
         
#define BUFSIZE 4096 
         
HANDLE hChildStdinRd, hChildStdinWr, hChildStdinWrDup, 
         
   hChildStdoutRd, hChildStdoutWr, hChildStdoutRdDup, 
         
   hInputFile, hStdout; 
         
BOOL CreateChildProcess(VOID); 
         
VOID WriteToPipe(VOID); 
         
VOID ReadFromPipe(VOID); 
         
VOID ErrorExit(LPTSTR); 
         
VOID ErrMsg(LPTSTR, BOOL); 
         
DWORD main(int argc, char *argv[]) 
         
{ 
         
   SECURITY_ATTRIBUTES saAttr; 
         
   BOOL fSuccess; 
         
// Set the bInheritHandle flag so pipe handles are inherited. 
         
   saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
         
   saAttr.bInheritHandle = TRUE; 
         
   saAttr.lpSecurityDescriptor = NULL; 
         
// Get the handle to the current STDOUT.  
         
   hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 
         
// Create a pipe for the child process's STDOUT.  
         
   if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) 
         
      ErrorExit("Stdout pipe creation failed/n"); 
         
// Create noninheritable read handle and close the inheritable read 
         
// handle. 
         
    fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdoutRd,
         
        GetCurrentProcess(), &hChildStdoutRdDup , 0,
         
        FALSE,
         
        DUPLICATE_SAME_ACCESS);
         
    if( !fSuccess )
         
        ErrorExit("DuplicateHandle failed");
         
    CloseHandle(hChildStdoutRd);
         
// Create a pipe for the child process's STDIN.  
         
   if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) 
         
      ErrorExit("Stdin pipe creation failed/n"); 
         
// Duplicate the write handle to the pipe so it is not inherited.  
         
   fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdinWr, 
         
      GetCurrentProcess(), &hChildStdinWrDup, 0, 
         
      FALSE,                  // not inherited 
         
      DUPLICATE_SAME_ACCESS); 
         
   if (! fSuccess) 
         
      ErrorExit("DuplicateHandle failed"); 
         
   CloseHandle(hChildStdinWr); 
         
// Now create the child process.    
         
   fSuccess = CreateChildProcess();
         
   if (! fSuccess) 
         
      ErrorExit("Create process failed"); 
         
// Get a handle to the parent's input file.  
         
   if (argc == 1) 
         
      ErrorExit("Please specify an input file");
         
   hInputFile = CreateFile(argv[1], GENERIC_READ, 0, NULL, 
         
      OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); 
         
   if (hInputFile == INVALID_HANDLE_VALUE) 
         
      ErrorExit("CreateFile failed/n"); 
         
// Write to pipe that is the standard input for a child process.  
         
   WriteToPipe(); 
         
// Read from pipe that is the standard output for child process.  
         
   ReadFromPipe(); 
         
   return 0; 
         
} 
         
BOOL CreateChildProcess() 
         
{ 
         
   PROCESS_INFORMATION piProcInfo; 
         
   STARTUPINFO siStartInfo;
         
   BOOL bFuncRetn = FALSE; 
         
// Set up members of the PROCESS_INFORMATION structure.  
         
   ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
         
// Set up members of the STARTUPINFO structure.  
         
   ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
         
   siStartInfo.cb = sizeof(STARTUPINFO); 
         
   siStartInfo.hStdError = hChildStdoutWr;
         
   siStartInfo.hStdOutput = hChildStdoutWr;
         
   siStartInfo.hStdInput = hChildStdinRd;
         
   siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
         
// Create the child process.     
         
   bFuncRetn = CreateProcess(NULL, 
         
      "child",       // command line 
         
      NULL,          // process security attributes 
         
      NULL,          // primary thread security attributes 
         
      TRUE,          // handles are inherited 
         
      0,             // creation flags 
         
      NULL,          // use parent's environment 
         
      NULL,          // use parent's current directory 
         
      &siStartInfo,  // STARTUPINFO pointer 
         
      &piProcInfo);  // receives PROCESS_INFORMATION 
         
   if (bFuncRetn == 0) 
         
      ErrorExit("CreateProcess failed");
         
   else 
         
   {
         
      CloseHandle(piProcInfo.hProcess);
         
      CloseHandle(piProcInfo.hThread);
         
      return bFuncRetn;
         
   }
         
}
         
VOID WriteToPipe(VOID) 
         
{ 
         
   DWORD dwRead, dwWritten; 
         
   CHAR chBuf[BUFSIZE]; 
         
// Read from a file and write its contents to a pipe.  
         
   for (;;) 
         
   { 
         
      if (! ReadFile(hInputFile, chBuf, BUFSIZE, &dwRead, NULL) || 
         
         dwRead == 0) break; 
         
      if (! WriteFile(hChildStdinWrDup, chBuf, dwRead, 
         
         &dwWritten, NULL)) break; 
         
   } 
         
// Close the pipe handle so the child process stops reading.  
         
   if (! CloseHandle(hChildStdinWrDup)) 
         
      ErrorExit("Close pipe failed"); 
         
} 
         
VOID ReadFromPipe(VOID) 
         
{ 
         
   DWORD dwRead, dwWritten; 
         
   CHAR chBuf[BUFSIZE]; 
         
// Close the write end of the pipe before reading from the 
         
// read end of the pipe.  
         
   if (!CloseHandle(hChildStdoutWr)) 
         
      ErrorExit("CloseHandle failed"); 
         
// Read output from the child process, and write to parent's STDOUT.  
         
   for (;;) 
         
   { 
         
      if( !ReadFile( hChildStdoutRdDup, chBuf, BUFSIZE, &dwRead, 
         
         NULL) || dwRead == 0) break; 
         
      if (! WriteFile(hStdout, chBuf, dwRead, &dwWritten, NULL)) 
         
         break; 
         
   } 
         
} 
         
VOID ErrorExit (LPTSTR lpszMessage) 
         
{ 
         
   fprintf(stderr, "%s/n", lpszMessage); 
         
   ExitProcess(0); 
         
}
         

下面是子程序的代碼:

The following is the code for the child process. It uses the inherited handles for STDIN and STDOUT to access the pipe created by the parent. The parent process reads from its input file and writes the information to a pipe. The child receives text through the pipe using STDIN and writes to the pipe using STDOUT. The parent reads from the read end of the pipe and displays the information to its STDOUT.

#include <windows.h> 
         
#define BUFSIZE 4096 
         
VOID main(VOID) 
         
{ 
         
   CHAR chBuf[BUFSIZE]; 
         
   DWORD dwRead, dwWritten; 
         
   HANDLE hStdin, hStdout; 
         
   BOOL fSuccess; 
         
   hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 
         
   hStdin = GetStdHandle(STD_INPUT_HANDLE); 
         
   if ((hStdout == INVALID_HANDLE_VALUE) || 
         
      (hStdin == INVALID_HANDLE_VALUE)) 
         
      ExitProcess(1); 
         
   for (;;) 
         
   { 
         
   // Read from standard input. 
         
      fSuccess = ReadFile(hStdin, chBuf, BUFSIZE, &dwRead, NULL); 
         
      if (! fSuccess || dwRead == 0) 
         
         break;  
         
   // Write to standard output. 
         
      fSuccess = WriteFile(hStdout, chBuf, dwRead, &dwWritten, NULL); 
         
      if (! fSuccess) 
         
         break; 
         
   } 
         
}
         

繼續閱讀