管道是进程通信用的共享内存的一部分,有两种用于双向通信的管道:匿名管道和命名管道。前者使得有亲属关系的进程能传递信息,一般常用来重定向子进程的标准输入或输出,这样子进程就可以与其父进程交换数据。为了能双向通信,必须创建两个匿名管道。父进程使用管道的写句柄写入数据到第一个管道,而子进程使用管道的读句柄从第一个管道中读出数据。类似地,子进程写入数据到第二个管道,而父进程从第二个管道读取数据。匿名管道不能在网络中使用,也不能在彼此无关的进程间使用。
命名管道用来在彼此间无关的进程和不同计算机上的进程之间传输数据。一般地,命名管道服务器进程使用一个众所周知的名称或能通知给各个客户端的名称来创建一个命名管道。命名管道客户进程只要知道服务器创建的管道的名称就可以打开这个管道的另一端。当服务器和客户都连接到管道上后,他们就可以通过对管道的读写来交换数据。
CreatePipe函数创建一个匿名管道,返回两个句柄:管道读句柄和管道写句柄。读句柄对管道只有只读权限,写句柄对管道只有只写权限。为了使用管道通信,管道服务器必须传递管道句柄给另一个进程,一般这通过继承来实现。这就是说,进程允许句柄能被子进程继承。进程也可以使用DuplicateHandle函数复制一个管道句柄并使用一些进程间通信方法,比如DDE或共享内存,将管道句柄传递给一个无关的进程。
管道服务器既可以传送读句柄或写句柄给管道客户,这取决于客户使用匿名管道发送还是接收信息。为了从管道读数据,客户在ReadFile函数中使用管道的读句柄。若有另一个进程在往管道中写数据时,ReadFile函数立马返回。若管道的所有的写句柄已经被关闭或读操作完成前发生错误,ReadFile函数也立马返回。
为了往管道中写数据,在WriteFile函数中使用管道的写句柄。直到指定数量的字节被写入管道中前或错误发生时,WriteFile函数才返回。若管道缓冲区满了而还有数据要待写入,WriteFile就会等另一个进程从管道中读取出数据后,从而空出更多的缓冲区空间时才返回。管道服务器在调用CreatePipe函数时指定管道的缓冲区大小。
匿名管道不支持异步读写操作(overlapped)。这意味着你不能在匿名管道上使用ReadFileEx和WriteFileEx。此外,当使用匿名管道时,ReadFile和WriteFile的lpOverlapped参数会被忽略。
当所有的管道句柄(读句柄和写句柄)都被关闭后,匿名管道才不存在。进程可以使用CloseHandle函数来关闭管道句柄。当进程终止时,所有的管道句柄也被关闭。
匿名管道使用一个拥有唯一名字的命名管道实现,因此,对于那些需要一个命名管道句柄的函数来说,你可以传递匿名管道的句柄给它们。
管道服务器通过如下方式控制它的句柄能否被继承:
1, CreatePipe函数接受一个SECURITY_ATTRIBUTES结构体,若管道服务器将bInheritHandle设置为TRUE,则创建的句柄可以被继承。
2,使用DuplicateHandle函数来改变一个管道句柄的继承性。
3,CreateProcess函数允许管道服务器指明子进程是否继承或不继承它所有的可继承的句柄。
当子进程继承了一个管道句柄,系统允许进程访问管道。然而,父进程必须将句柄值交流给子进程。这点父进程一般通过重定向标准输出到子进程来实现,步骤如下:
1, 调用GetStdHandle函数获取当前的标准输出句柄,保存这个句柄以便在子进程创建完后可以恢复原来的标准输出句柄。
2, 调用SetStdHandle函数设置标准输出句柄为管道的写句柄,现在父进程可以创建子进程了。
3, 调用CloseHandle函数关闭管道的写句柄,当子进程继承写句柄后,父进程就不需要它的拷贝了。
4, 调用SetStdHandle恢复原来的标准输出句柄。
子进程使用GetStdHandle函数来得到它的标准输出句柄,而现在它是管道的写端的句柄。子进程使用WriteFile函数将它的数据写到管道中,当子进程使用完管道后,它应该调用CloseHandle来关闭管道句柄或直接终止进程,后者会自动关闭句柄的。
父进程使用ReadFile函数来从管道中接收数据。数据是以字节流的形式写入匿名句柄中的,这就意味着从管道中读数据的父进程可以在几次写数据操作中无法分辨开来,除非父子进程使用一个协议来指明写操作何时完成。当管道所有的写句柄关闭后,ReadFile函数返回0.对于父进程来说,重要的一点是,在调用ReadFile前,应该关闭管道的写端的所有句柄。若这个不做的话,ReadFile操作就无法返回0,因为父进程还有到管道写端的打开句柄。
重定向标准输入句柄类似于重定向输出句柄,管道的读句柄作为子进程的标准输入句柄使用。这时,父进程必须确保子进程没有继承管道的写句柄。若不然,子进程的ReadFile操作无法返回0,因为子进程有到管道的写端的打开句柄。
父进程:
#include <tchar.h>
#include <stdio.h>
#define BUFSIZE 4096
HANDLE hChildStdinRd, hChildStdinWr,
hChildStdoutRd, hChildStdoutWr,
hInputFile, hStdout;
BOOL CreateChildProcess(VOID);
VOID WriteToPipe(VOID);
VOID ReadFromPipe(VOID);
VOID ErrorExit(LPSTR);
int _tmain(int argc, TCHAR *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");
// Ensure the read handle to the pipe for STDOUT is not inherited.
SetHandleInformation( hChildStdoutRd, HANDLE_FLAG_INHERIT, 0);
// Create a pipe for the child process's STDIN.
if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
ErrorExit("Stdin pipe creation failed\n");
// Ensure the write handle to the pipe for STDIN is not inherited.
SetHandleInformation( hChildStdinWr, HANDLE_FLAG_INHERIT, 0);
// Now create the child process.
fSuccess = CreateChildProcess();
if (! fSuccess)
ErrorExit("Create process failed with");
// Get a handle to the parent's input file.
if (argc == 1)
ErrorExit("Please specify an input file");
printf( "\nContents of %s:\n\n", argv[1]);
hInputFile = CreateFile(argv[1], GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
if (hInputFile == INVALID_HANDLE_VALUE)
ErrorExit("CreateFile failed");
// 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()
{
TCHAR szCmdline[]=TEXT("child");
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,
szCmdline, // 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\n");
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(hChildStdinWr, chBuf, dwRead,
&dwWritten, NULL)) break;
}
// Close the pipe handle so the child process stops reading.
if (! CloseHandle(hChildStdinWr))
ErrorExit("Close pipe failed\n");
}
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("Closing handle failed");
// Read output from the child process, and write to parent's STDOUT.
for (;;)
{
if( !ReadFile( hChildStdoutRd, chBuf, BUFSIZE, &dwRead,
NULL) || dwRead == 0) break;
if (! WriteFile(hStdout, chBuf, dwRead, &dwWritten, NULL))
break;
}
}
VOID ErrorExit (LPSTR lpszMessage)
{
fprintf(stderr, "%s\n", lpszMessage);
ExitProcess(0);
}
子进程:
#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;
}
}