进程间通信--管道篇

简介:
管道是进程通信用的共享内存的一部分,有两种用于双向通信的管道:匿名管道和命名管道。前者使得有亲属关系的进程能传递信息,一般常用来重定向子进程的标准输入或输出,这样子进程就可以与其父进程交换数据。为了能双向通信,必须创建两个匿名管道。父进程使用管道的写句柄写入数据到第一个管道,而子进程使用管道的读句柄从第一个管道中读出数据。类似地,子进程写入数据到第二个管道,而父进程从第二个管道读取数据。匿名管道不能在网络中使用,也不能在彼此无关的进程间使用。
命名管道用来在彼此间无关的进程和不同计算机上的进程之间传输数据。一般地,命名管道服务器进程使用一个众所周知的名称或能通知给各个客户端的名称来创建一个命名管道。命名管道客户进程只要知道服务器创建的管道的名称就可以打开这个管道的另一端。当服务器和客户都连接到管道上后,他们就可以通过对管道的读写来交换数据。
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 <windows.h> 
#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); 
}


子进程:
#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; 
   } 
}



本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2008/06/11/1217557.html,如需转载请自行联系原作者
目录
打赏
0
0
0
0
60
分享
相关文章
从零开始掌握进程间通信:管道、信号、消息队列、共享内存大揭秘
本文详细介绍了进程间通信(IPC)的六种主要方式:管道、信号、消息队列、共享内存、信号量和套接字。每种方式都有其特点和适用场景,如管道适用于父子进程间的通信,消息队列能传递结构化数据,共享内存提供高速数据交换,信号量用于同步控制,套接字支持跨网络通信。通过对比和分析,帮助读者理解并选择合适的IPC机制,以提高系统性能和可靠性。
372 14
进程间通信方式-----管道通信
【10月更文挑战第29天】管道通信是一种重要的进程间通信机制,它为进程间的数据传输和同步提供了一种简单有效的方法。通过合理地使用管道通信,可以实现不同进程之间的协作,提高系统的整体性能和效率。
【Linux 系统】进程间通信(匿名管道 & 命名管道)-- 详解(下)
【Linux 系统】进程间通信(匿名管道 & 命名管道)-- 详解(下)
【Linux 系统】进程间通信(匿名管道 & 命名管道)-- 详解(上)
【Linux 系统】进程间通信(匿名管道 & 命名管道)-- 详解(上)
C语言 多进程编程(二)管道
本文详细介绍了Linux下的进程间通信(IPC),重点讨论了管道通信机制。首先,文章概述了进程间通信的基本概念及重要性,并列举了几种常见的IPC方式。接着深入探讨了管道通信,包括无名管道(匿名管道)和有名管道(命名管道)。无名管道主要用于父子进程间的单向通信,有名管道则可用于任意进程间的通信。文中提供了丰富的示例代码,展示了如何使用`pipe()`和`mkfifo()`函数创建管道,并通过实例演示了如何利用管道进行进程间的消息传递。此外,还分析了管道的特点、优缺点以及如何通过`errno`判断管道是否存在,帮助读者更好地理解和应用管道通信技术。
Linux进程间通信秘籍:管道、消息队列、信号量,一文让你彻底解锁!
【8月更文挑战第25天】本文概述了Linux系统中常用的五种进程间通信(IPC)模式:管道、消息队列、信号量、共享内存与套接字。通过示例代码展示了每种模式的应用场景。了解这些IPC机制及其特点有助于开发者根据具体需求选择合适的通信方式,促进多进程间的高效协作。
307 3
已解决:连接SqlServer出现 provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程【C#连接SqlServer踩坑记录】
本文介绍了解决连接SqlServer时出现“provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程”错误的步骤,包括更改服务器验证模式、修改sa用户设置、启用TCP/IP协议,以及检查数据库连接语句中的实例名是否正确。此外,还解释了实例名mssqlserver和sqlserver之间的区别,包括它们在默认设置、功能和用途上的差异。
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
133 0
从管道路由到共享内存:进程间通信的演变(内附通信方式经典面试题及详解)
进程间通信(Inter-Process Communication, IPC)是计算机科学中的一个重要概念,指的是运行在同一系统或不同系统上的多个进程之间互相发送和接收信息的能力。IPC机制允许进程间共享数据、协调执行流程,是实现分布式系统、多任务操作系统和并发编程的基础。
122 0
从管道路由到共享内存:进程间通信的演变(内附通信方式经典面试题及详解)
|
8月前
|
Python IPC深度探索:解锁跨进程通信的无限可能,以管道与队列为翼,让你的应用跨越边界,无缝协作,震撼登场
【8月更文挑战第3天】Python IPC大揭秘:解锁进程间通信新姿势,让你的应用无界连接
47 0

相关实验场景

更多