Windows系统编程之进程间通信
作者:北极星2003
来源:看雪论坛(www.pediy.com)
Windows 的IPC(进程间通信)机制主要是异步管道和命名管道。(至于其他的IPC方式,例如内存映射、邮槽等这里就不介绍了)
管道(pipe)是用于进程间通信的共享内存区域。创建管道的进程称为管道服务器,而连接到这个管道的进程称为管道客户端。一个进程向管道写入信息,而另外一个进程从管道读取信息。
异步管道是基于字符和半双工的(即单向),一般用于程序输入输出的重定向;命名管道则强大地多,它们是面向消息和全双工的,同时还允许网络通信,用于创建客户端/服务器系统。
一、异步管道(实现比较简单,直接通过实例来讲解)
实验目标:当前有sample.cpp, sample.exe, sample.in这三个文件,sample.exe为sample.cpp的执行程序,sample.cpp只是一个简单的程序示例(简单求和),如下:

代码:
  1. #include <iostream.h>
  2. int main()
  3. {
  4. int a, b ;
  5. while ( cin >> a >> b && ( a || b ) )
  6. cout << a + b << endl ;
  7. return 0;
  8. }
Sample.in文件是输入文件,内容:
32 433
542 657
0 0
要求根据sample.exe和它的输入数据,把输出数据重定向到sample.out
流程分析:实际这个实验中包含两个部分,把输入数据重定向到sample.exe 和把输出数据重定向到sample.out。在命令行下可以很简单的实现这个功能“sample <sample.in >sample.out”,这个命令也是利用管道特性实现的,现在我们就根据异步管道的实现原理自己来实现这个功能。
管道是基于半双工(单向)的,这里有两个重定向的过程,显然需要创建两个管道,下面给出流程图:
异步管道实现的流程图说明:
1)。父进程是我们需要实现的,其中需要创建管道A,管道B,和子进程,整个实现流程分为4个操作。
2)。管道A:输入管道
3)。管道B:输出管道
4)。操作A:把输入文件sample.in的数据写入输入管道(管道A)
5)。操作B:子进程从输入管道中读取数据,作为该进程的加工原料。通常,程序的输入数据由标准的输入设备输入,这里实现输入重定向,即把输入管道作为输入设备。
6)。操作C:子进程把加工后的成品(输出数据)输出到输出管道。通常,程序的输出数据会输出到标准的输出设备,一般为屏幕,这里实现输出重定向,即把输出管道作为输出设备。
7)。操作D:把输出管道的数据写入输出文件
需要注意的是,管道的本质只是一个共享的内存区域。这个实验中,管道区域处于父进程的地址空间中,父进程的作用是提供环境和资源,并协调子进程进行加工。
程序源码:

代码:

  1. #include <windows.h>
  2. #include <iostream.h>
  3. const int BUFSIZE = 4096 ;
  4. HANDLE  hChildStdinRd, hChildStdinWr, hChildStdinWrDup,
  5. hChildStdoutRd,hChildStdoutWr,hChildStdoutRdDup,
  6. hSaveStdin,    hSaveStdout;
  7. BOOL CreateChildProcess(LPTSTR);
  8. VOID WriteToPipe(LPTSTR);
  9. VOID ReadFromPipe(LPTSTR);
  10. VOID ErrorExit(LPTSTR);
  11. VOID ErrMsg(LPTSTR, BOOL);
  12. void main( int argc, char *argv[] )
  13. {
  14. // 处理输入参数
  15. if ( argc != 4 )
  16. return ;
  17. // 分别用来保存命令行,输入文件名(CPP/C),输出文件名(保存编译信息)
  18. LPTSTR lpProgram = new char[ strlen(argv[1]) ] ;
  19. strcpy ( lpProgram, argv[1] ) ;
  20. LPTSTR lpInputFile = new char[ strlen(argv[2]) ];
  21. strcpy ( lpInputFile, argv[2] ) ;
  22. LPTSTR lpOutputFile = new char[ strlen(argv[3]) ] ;
  23. strcpy ( lpOutputFile, argv[3] ) ;
  24. SECURITY_ATTRIBUTES saAttr;
  25. saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
  26. saAttr.bInheritHandle = TRUE;
  27. saAttr.lpSecurityDescriptor = NULL;
  28. /************************************************
  29. *    redirecting child process's STDOUT  *
  30. ************************************************/
  31. hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE);
  32. if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
  33. ErrorExit("Stdout pipe creation failed/n");
  34. if (! SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr))
  35. ErrorExit("Redirecting STDOUT failed");
  36. BOOL fSuccess = DuplicateHandle(
  37. GetCurrentProcess(),
  38. hChildStdoutRd,
  39. GetCurrentProcess(),
  40. &hChildStdoutRdDup ,
  41. 0,
  42. FALSE,
  43. DUPLICATE_SAME_ACCESS);
  44. if( !fSuccess )
  45. ErrorExit("DuplicateHandle failed");
  46. CloseHandle(hChildStdoutRd);
  47. /************************************************
  48. *    redirecting child process's STDIN    *
  49. ************************************************/
  50. hSaveStdin = GetStdHandle(STD_INPUT_HANDLE);
  51. if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
  52. ErrorExit("Stdin pipe creation failed/n");
  53. if (! SetStdHandle(STD_INPUT_HANDLE, hChildStdinRd))
  54. ErrorExit("Redirecting Stdin failed");
  55. fSuccess = DuplicateHandle(
  56. GetCurrentProcess(),
  57. hChildStdinWr,
  58. GetCurrentProcess(),
  59. &hChildStdinWrDup,
  60. 0,
  61. FALSE,
  62. DUPLICATE_SAME_ACCESS);
  63. if (! fSuccess)
  64. ErrorExit("DuplicateHandle failed");
  65. CloseHandle(hChildStdinWr);
  66. /************************************************
  67. *      创建子进程(即启动SAMPLE.EXE)    *
  68. ************************************************/
  69. fSuccess = CreateChildProcess( lpProgram );
  70. if ( !fSuccess )
  71. ErrorExit("Create process failed");
  72. // 父进程输入输出流的还原设置
  73. if (! SetStdHandle(STD_INPUT_HANDLE, hSaveStdin))
  74. ErrorExit("Re-redirecting Stdin failed/n");
  75. if (! SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout))
  76. ErrorExit("Re-redirecting Stdout failed/n");
  77. WriteToPipe( lpInputFile ) ;
  78. ReadFromPipe( lpOutputFile );
  79. delete lpProgram ;
  80. delete lpInputFile ;
  81. delete lpOutputFile ;
  82. }
  83. BOOL CreateChildProcess( LPTSTR lpProgram )
  84. {
  85. PROCESS_INFORMATION piProcInfo;
  86. STARTUPINFO siStartInfo;
  87. BOOL bFuncRetn = FALSE;
  88. ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
  89. ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
  90. siStartInfo.cb = sizeof(STARTUPINFO);
  91. bFuncRetn = CreateProcess ( NULL, lpProgram, NULL, NULL, TRUE, /
  92. 0, NULL, NULL, &siStartInfo, &piProcInfo);
  93. if (bFuncRetn == 0)
  94. {
  95. ErrorExit("CreateProcess failed/n");
  96. return 0;
  97. }
  98. else
  99. {
  100. CloseHandle(piProcInfo.hProcess);
  101. CloseHandle(piProcInfo.hThread);
  102. return bFuncRetn;
  103. }
  104. }
  105. VOID WriteToPipe( LPTSTR lpInputFile )
  106. {
  107. HANDLE hInputFile = CreateFile(lpInputFile, GENERIC_READ, 0, NULL,
  108. OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
  109. if (hInputFile == INVALID_HANDLE_VALUE)
  110. return ;
  111. BOOL fSuccess ;
  112. DWORD dwRead, dwWritten;
  113. CHAR chBuf[BUFSIZE] = {0} ;
  114. for (;;)
  115. {
  116. fSuccess = ReadFile( hInputFile, chBuf, BUFSIZE, &dwRead, NULL) ;
  117. if ( !fSuccess || dwRead == 0)
  118. break;
  119. fSuccess = WriteFile( hChildStdinWrDup, chBuf, dwRead, &dwWritten, NULL) ;
  120. if ( !fSuccess )
  121. break;
  122. }
  123. if (! CloseHandle(hChildStdinWrDup))
  124. ErrorExit("Close pipe failed/n");
  125. CloseHandle ( hInputFile ) ;
  126. }
  127. VOID ReadFromPipe( LPTSTR lpOutputFile )
  128. {
  129. HANDLE hOutputFile = CreateFile( lpOutputFile, GENERIC_READ|GENERIC_WRITE,
  130. FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  131. if (hOutputFile == INVALID_HANDLE_VALUE)
  132. return ;
  133. BOOL fSuccess ;
  134. DWORD dwRead, dwWritten;
  135. CHAR chBuf[BUFSIZE] = { 0 };
  136. if (!CloseHandle(hChildStdoutWr))
  137. ErrorExit("Closing handle failed");
  138. for (;;)
  139. {
  140. fSuccess = ReadFile( hChildStdoutRdDup, chBuf, BUFSIZE, &dwRead, NULL) ;
  141. if( !fSuccess || dwRead == 0)
  142. {
  143. break;
  144. }
  145. fSuccess = WriteFile( hOutputFile, chBuf, dwRead, &dwWritten, NULL) ;
  146. if ( !fSuccess )
  147. break;
  148. }
  149. CloseHandle ( hOutputFile ) ;
  150. }
  151. VOID ErrorExit (LPTSTR lpszMessage)
  152. {
  153. MessageBox( 0, lpszMessage, 0, 0 );
  154. }
二、命名管道
命名管道具有以下几个特征:
(1)命名管道是双向的,所以两个进程可以通过同一管道进行交互。
(2)命名管道不但可以面向字节流,还可以面向消息,所以读取进程可以读取写进程发送的不同长度的消息。
(3)多个独立的管道实例可以用一个名称来命名。例如几个客户端可以使用名称相同的管道与同一个服务器进行并发通信。
(4)命名管道可以用于网络间两个进程的通信,而其实现的过程与本地进程通信完全一致。
实验目标:在客户端输入数据a和b,然后发送到服务器并计算a+b,然后把计算结果发送到客户端。可以多个客户端与同一个服务器并行通信。
界面设计:
难点所在:
实现的过程比较简单,但有一个难点。原本当服务端使用ConnectNamedPipe函数后,如果有客户端连接,就可以直接进行交互。原来我在实现过程中,当管道空闲时,管道的线程函数会无限(INFINITE)阻塞。若现在需要停止服务,就必须结束所有的线程,TernimateThread可以作为一个结束线程的方法,但我基本不用这个函数。一旦使用这个函数之后,目标线程就会立即结束,但如果此时的目标线程正在操作互斥资源、内核调用、或者是操作共享DLL的全局变量,可能会出现互斥资源无法释放、内核异常等现象。这里我用重叠I/0来解决这个问题,在创建PIPE时使用FILE_FLAG_OVERLAPPED标志,这样使用ConnectNamedPipe后会立即返回,但线程的阻塞由等待函数WaitForSingleObject来实现,等待OVERLAPPED结构的事件对象被设置。
客户端主要代码:

代码:
  1. void CMyDlg::OnSubmit()
  2. {
  3. // 打开管道
  4. HANDLE hPipe = CreateFile("////.//Pipe//NamedPipe", GENERIC_READ | GENERIC_WRITE, /
  5. 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) ;
  6. if ( hPipe == INVALID_HANDLE_VALUE )
  7. {
  8. this->MessageBox ( "打开管道失败,服务器尚未启动,或者客户端数量过多" ) ;
  9. return ;
  10. }
  11. DWORD nReadByte, nWriteByte ;
  12. char szBuf[1024] = {0} ;
  13. // 把两个整数(a,b)格式化为字符串
  14. sprintf ( szBuf, "%d %d", this->nFirst, this->nSecond ) ;
  15. // 把数据写入管道
  16. WriteFile ( hPipe, szBuf, strlen(szBuf), &nWriteByte, NULL ) ;
  17. memset ( szBuf, 0, sizeof(szBuf) ) ;
  18. // 读取服务器的反馈信息
  19. ReadFile ( hPipe, szBuf, 1024, &nReadByte, NULL ) ;
  20. // 把返回信息格式化为整数
  21. sscanf ( szBuf, "%d", &(this->nResValue) ) ;
  22. this->UpdateData ( false ) ;
  23. CloseHandle ( hPipe ) ;
  24. }
服务端主要代码:

代码:
  1. // 启动服务
  2. void CMyDlg::OnStart()
  3. {
  4. CString lpPipeName = "////.//Pipe//NamedPipe" ;
  5. for ( UINT i = 0; i < nMaxConn; i++ )
  6. {
  7. // 创建管道实例
  8. PipeInst[i].hPipe =  CreateNamedPipe ( lpPipeName, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, /
  9. PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, nMaxConn, 0, 0, 1000, NULL ) ;
  10. if ( PipeInst[i].hPipe == INVALID_HANDLE_VALUE )
  11. {
  12. DWORD dwErrorCode = GetLastError () ;
  13. this->MessageBox ( "创建管道错误!" ) ;
  14. return ;
  15. }
  16. // 为每个管道实例创建一个事件对象,用于实现重叠IO
  17. PipeInst[i].hEvent  =  CreateEvent ( NULL, false, false, false ) ;
  18. // 为每个管道实例分配一个线程,用于响应客户端的请求
  19. PipeInst[i].hTread = AfxBeginThread ( ServerThread, &PipeInst[i], THREAD_PRIORITY_NORMAL ) ;
  20. }
  21. this->SetWindowText ( "命名管道实例之服务器(运行)" ) ;
  22. this->MessageBox ( "服务启动成功" ) ;
  23. }
  24. // 停止服务
  25. void CMyDlg::OnStop()
  26. {
  27. DWORD dwNewMode = PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_NOWAIT ;
  28. for ( UINT i = 0; i < nMaxConn; i++ )
  29. {
  30. SetEvent ( PipeInst[i].hEvent ) ;
  31. CloseHandle ( PipeInst[i].hTread ) ;
  32. CloseHandle ( PipeInst[i].hPipe ) ;
  33. }
  34. this->SetWindowText ( "命名管道实例之服务器" ) ;
  35. this->MessageBox ( "停止启动成功" ) ;
  36. }
  37. // 线程服务函数
  38. UINT ServerThread ( LPVOID lpParameter )
  39. {
  40. DWORD  nReadByte = 0, nWriteByte = 0, dwByte = 0 ;
  41. char  szBuf[MAX_BUFFER_SIZE] = {0} ;
  42. PIPE_INSTRUCT  CurPipeInst = *(PIPE_INSTRUCT*)lpParameter ;
  43. OVERLAPPED OverLapStruct = { 0, 0, 0, 0, CurPipeInst.hEvent } ;
  44. while ( true )
  45. {
  46. memset ( szBuf, 0, sizeof(szBuf) ) ;
  47. // 命名管道的连接函数,等待客户端的连接(只针对NT)
  48. ConnectNamedPipe ( CurPipeInst.hPipe, &OverLapStruct ) ;
  49. // 实现重叠I/0,等待OVERLAPPED结构的事件对象
  50. WaitForSingleObject ( CurPipeInst.hEvent, INFINITE ) ;
  51. // 检测I/0是否已经完成,如果未完成,意味着该事件对象是人工设置,即服务需要停止
  52. if ( !GetOverlappedResult ( CurPipeInst.hPipe, &OverLapStruct, &dwByte, true ) )
  53. break ;
  54. // 从管道中读取客户端的请求信息
  55. if ( !ReadFile ( CurPipeInst.hPipe, szBuf, MAX_BUFFER_SIZE, &nReadByte, NULL ) )
  56. {
  57. MessageBox ( 0, "读取管道错误!", 0, 0 ) ;
  58. break ;
  59. }
  60. int a, b ;
  61. sscanf ( szBuf, "%d %d", &a, &b ) ;
  62. pMyDlg->nFirst    = a ;
  63. pMyDlg->nSecond    = b ;
  64. pMyDlg->nResValue  = a + b ;
  65. memset ( szBuf, 0, sizeof(szBuf) ) ;
  66. sprintf ( szBuf, "%d", pMyDlg->nResValue ) ;
  67. // 把反馈信息写入管道
  68. WriteFile ( CurPipeInst.hPipe, szBuf, strlen(szBuf), &nWriteByte, NULL ) ;
  69. pMyDlg->SetDlgItemInt ( IDC_FIRST, a, true ) ;
  70. pMyDlg->SetDlgItemInt ( IDC_SECOND, b, true ) ;
  71. pMyDlg->SetDlgItemInt ( IDC_RESULT, pMyDlg->nResValue, true ) ;
  72. // 断开客户端的连接,以便等待下一客户的到来
  73. DisconnectNamedPipe ( CurPipeInst.hPipe ) ;
  74. }
  75. return 0 ;
  76. }
最后特别说明一下,此文章是看雪WIN32安全编程板块的斑竹北极星的,大家可以多多关注一下他的技术文章,看了几篇都认为很不错的。
链 接: http://bbs.pediy.com/showthread.php?t=26252
http://blog.csdn.net/yiruirui0507/article/details/6457806

Windows系统编程之进程间通信的更多相关文章

  1. Windows系统编程之异步I/O和完成端口

    Windows系统编程之异步I/O和完成端口[作者]北极星2003[来源]看雪技术论坛(bbs.pediy.com) [时间]2006年7月1日 一.  同步I/O和异步I/O 在介绍这部分内容之前先 ...

  2. Windows系统编程之进程同步试验

    试验过程中调用了不少系统函数,并且涉及到一些系统级的概念,在此记录下来做为解决问题的一种方式.也许在以后的编程的过程中是否可以通过调用系统平台上的东西来完成一些任务,这仍不失为一种好的思维方式. 多线 ...

  3. linux系统编程:进程间通信-mmap

    进程间通信-mmap #include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, in ...

  4. linux系统编程:进程间通信-fifo

    进程间通信-fifo 进程间通信的还有一种方式是fifo. fifo是还有一种管道:有名管道.从名字能够看出.它也是队列. 使用fifo通信前,得先创建fifo $ mkfifo myfifo 随后仅 ...

  5. 编程实现Windows系统自动登录

    编程实现Windows系统自动登录 原理: 通过注册表修改实现.Windows内置了自动登录的机制,在登录系统时,winlogon会检查注册表下有没有设置自动登录,如果设置了就上就会读取用户名和密码, ...

  6. 【Windows核心编程】一个使用内存映射文件进行进程间通信的例子

    进程间通信的方式有很多种,其底层原理使用的都是内存映射文件. 本文实现了Windows核心编程第五版475页上的demo,即使用内存映射文件来在进程间通信. 进程1 按钮[Create  mappin ...

  7. Linux系统编程——进程间通信:管道(pipe)

    管道的概述 管道也叫无名管道,它是是 UNIX 系统 IPC(进程间通信) 的最古老形式,全部的 UNIX 系统都支持这样的通信机制. 无名管道有例如以下特点: 1.半双工,数据在同一时刻仅仅能在一个 ...

  8. Python 编程环境搭建(Windows 系统中)

    由于大家普遍使用 Windows 系统,所以本文只介绍 Windows 系统中 Python 环境的安装. 在 Windows 中安装 Python 与安装普通软件没什么差别,下载所需版本的安装包后, ...

  9. 《Python黑帽子:黑客与渗透测试编程之道》 Windows系统提权

    环境准备: pywin32的安装在第八章的键盘记录中有,这里还需要安装wmi: 在本人的32位win7上本来是没有easy_install这个命令的,这需要安装setuptools-0.6c11.wi ...

随机推荐

  1. Spring Boot(spring mvc升级版)

    周末挤出了一些时间,学了点东西,总结了一下,可能还有自己理解不到位的地方,希望大家一起交流学习,好东西要大家一起分享嘛~.时间有点紧张,所以样式没有来及做很好的调整,大家就凑活着看吧. Spring ...

  2. 常用的Linux操作一

    Linux 常用的操作必须明白. 1.ls  和ll 列出文件的目录. 2.tail -f XXX  查看文件. 3.chmod -R 777 XXX.jar 赋予权限 4.cat 查看文件 -n 对 ...

  3. 【protobuf进阶】读取proto实体里的extensionObject对象的方法

    //设置扩展对象 ProtoBuf.Extensible.AppendValue //读取扩展对象 ProtoBuf.Extensible.GetValue 最近通过C#的TcpClient调用jav ...

  4. .Net Framework 4.0安装cmd命令

    在安装系统以后和.Net FrameWork 后,通过cmd编译编写的程序时总是提示编译错误.可以通过cmd命令安装相应的.net framework版本. 具体步骤如下: 1.以管理员身份打开cmd ...

  5. 物联网MQTT协议分析和开源Mosquitto部署验证

    在<物联网核心协议—消息推送技术演进>一文中已向读者介绍了多种消息推送技术的情况,包括HTTP单向通信.Ajax轮询.Websocket.MQTT.CoAP等,其中MQTT协议为IBM制定 ...

  6. C++通过WIN32 API获取逻辑磁盘详细信息

      众所周知,在微软的操作系统下编写应用程序,最主要的还是通过windows所提供的api函数来实现各种操作的,这些函数通常是可以直接使用的,只要包含windows.h这个头文件. 今天我们主要介绍的 ...

  7. nginix 笔记

    1. 一个master进程,多个worker进程,worker进程数目可自动配置为核的数目 2. 配置文件ngnix.conf存放在linux的/etc/ngnix目录下

  8. 微信,QQ这类IM app怎么做——谈谈Websocket

    前言 关于我和WebSocket的缘:我从大二在计算机网络课上听老师讲过之后,第一次使用就到了毕业之后的第一份工作.直到最近换了工作,到了一家是含有IM社交聊天功能的app的时候,我觉得我现在可以谈谈 ...

  9. Android ViewPager 打造炫酷欢迎页

    Android ViewPager 打造炫酷欢迎页 ViewPager是Android扩展v4包中的类,这个类可以让用户切换当前的View.对于这个类的应用场景,稍加修改就可以应用到多个环境下.比如: ...

  10. 01-android快速入门

    adb Android debug bridge 安卓调试桥 创建模拟器,屏幕尽量小些,启动速度运行速度快 Android项目的目录结构 Activity:应用被打开时显示的界面 src:项目代码 R ...