设为首页 收藏本站
查看: 584|回复: 0

[经验分享] Windows系统编程之进程间通信

[复制链接]

尚未签到

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


代码:



[cpp] view plain copy

  • #include <iostream.h>  
  • int main()  
  • {
  •   int a, b ;  
  •   while ( cin >> a >> b && ( a || b ) )  
  •     cout << a + b << endl ;
  •   return 0;  
  • }

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


代码:


[cpp] view plain copy

  • #include <windows.h>   
  • #include <iostream.h>  

  • const int BUFSIZE = 4096 ;   
  • HANDLE  hChildStdinRd, hChildStdinWr, hChildStdinWrDup,   
  •        hChildStdoutRd,hChildStdoutWr,hChildStdoutRdDup,
  •     hSaveStdin,    hSaveStdout;

  • BOOL CreateChildProcess(LPTSTR);   
  • VOID WriteToPipe(LPTSTR);   
  • VOID ReadFromPipe(LPTSTR);   
  • VOID ErrorExit(LPTSTR);   
  • VOID ErrMsg(LPTSTR, BOOL);   
  • void main( int argc, char *argv[] )   
  • {
  •   // 处理输入参数  
  •   if ( argc != 4 )  
  •     return ;  

  •   // 分别用来保存命令行,输入文件名(CPP/C),输出文件名(保存编译信息)  
  •   LPTSTR lpProgram = new char[ strlen(argv[1]) ] ;  
  •   strcpy ( lpProgram, argv[1] ) ;
  •   LPTSTR lpInputFile = new char[ strlen(argv[2]) ];  
  •   strcpy ( lpInputFile, argv[2] ) ;
  •   LPTSTR lpOutputFile = new char[ strlen(argv[3]) ] ;  
  •   strcpy ( lpOutputFile, argv[3] ) ;

  •   SECURITY_ATTRIBUTES saAttr;
  •   saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);   
  •   saAttr.bInheritHandle = TRUE;
  •   saAttr.lpSecurityDescriptor = NULL;

  •   /************************************************
  •    *    redirecting child process's STDOUT  *
  •    ************************************************/  
  •   hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE);

  •   if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))   
  •     ErrorExit("Stdout pipe creation failed/n");   

  •   if (! SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr))   
  •     ErrorExit("Redirecting STDOUT failed");   

  •   BOOL fSuccess = DuplicateHandle(  
  •     GetCurrentProcess(),
  •     hChildStdoutRd,
  •         GetCurrentProcess(),
  •     &hChildStdoutRdDup ,
  •     0,
  •         FALSE,
  •         DUPLICATE_SAME_ACCESS);
  •     if( !fSuccess )  
  •         ErrorExit("DuplicateHandle failed");  
  •     CloseHandle(hChildStdoutRd);

  •   /************************************************
  •    *    redirecting child process's STDIN    *
  •    ************************************************/  
  •   hSaveStdin = GetStdHandle(STD_INPUT_HANDLE);

  •   if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))   
  •     ErrorExit("Stdin pipe creation failed/n");   

  •   if (! SetStdHandle(STD_INPUT_HANDLE, hChildStdinRd))   
  •     ErrorExit("Redirecting Stdin failed");   

  •   fSuccess = DuplicateHandle(
  •     GetCurrentProcess(),
  •     hChildStdinWr,
  •     GetCurrentProcess(),
  •     &hChildStdinWrDup,
  •     0,
  •     FALSE,
  •     DUPLICATE_SAME_ACCESS);
  •   if (! fSuccess)   
  •     ErrorExit("DuplicateHandle failed");   
  •   CloseHandle(hChildStdinWr);

  •   /************************************************
  •    *      创建子进程(即启动SAMPLE.EXE)    *
  •    ************************************************/  
  •   fSuccess = CreateChildProcess( lpProgram );
  •   if ( !fSuccess )   
  •     ErrorExit("Create process failed");   

  •   // 父进程输入输出流的还原设置  
  •   if (! SetStdHandle(STD_INPUT_HANDLE, hSaveStdin))   
  •     ErrorExit("Re-redirecting Stdin failed/n");   
  •   if (! SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout))   
  •     ErrorExit("Re-redirecting Stdout failed/n");   

  •   WriteToPipe( lpInputFile ) ;
  •   ReadFromPipe( lpOutputFile );
  •           delete lpProgram ;  
  •           delete lpInputFile ;  
  •           delete lpOutputFile ;  
  • }

  • BOOL CreateChildProcess( LPTSTR lpProgram )   
  • {
  •   PROCESS_INFORMATION piProcInfo;
  •   STARTUPINFO siStartInfo;
  •   BOOL bFuncRetn = FALSE;   

  •   ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );  
  •   ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );  
  •   siStartInfo.cb = sizeof(STARTUPINFO);   

  •   bFuncRetn = CreateProcess ( NULL, lpProgram, NULL, NULL, TRUE, /
  •                 0, NULL, NULL, &siStartInfo, &piProcInfo);
  •   if (bFuncRetn == 0)   
  •   {
  •     ErrorExit("CreateProcess failed/n");  
  •     return 0;  
  •   }
  •   else   
  •   {
  •     CloseHandle(piProcInfo.hProcess);
  •     CloseHandle(piProcInfo.hThread);
  •     return bFuncRetn;  
  •   }
  • }

  • VOID WriteToPipe( LPTSTR lpInputFile )   
  • {
  •   HANDLE hInputFile = CreateFile(lpInputFile, GENERIC_READ, 0, NULL,   
  •     OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
  •   if (hInputFile == INVALID_HANDLE_VALUE)   
  •     return ;  

  •   BOOL fSuccess ;  
  •   DWORD dwRead, dwWritten;   
  •   CHAR chBuf[BUFSIZE] = {0} ;   

  •   for (;;)   
  •   {
  •     fSuccess = ReadFile( hInputFile, chBuf, BUFSIZE, &dwRead, NULL) ;
  •     if ( !fSuccess || dwRead == 0)  
  •       break;   

  •     fSuccess = WriteFile( hChildStdinWrDup, chBuf, dwRead, &dwWritten, NULL) ;
  •     if ( !fSuccess )   
  •       break;   
  •   }

  •   if (! CloseHandle(hChildStdinWrDup))   
  •     ErrorExit("Close pipe failed/n");   

  •   CloseHandle ( hInputFile ) ;
  • }

  • VOID ReadFromPipe( LPTSTR lpOutputFile )   
  • {
  •   HANDLE hOutputFile = CreateFile( lpOutputFile, GENERIC_READ|GENERIC_WRITE,   
  •     FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  •   if (hOutputFile == INVALID_HANDLE_VALUE)   
  •     return ;  

  •   BOOL fSuccess ;  
  •   DWORD dwRead, dwWritten;   
  •   CHAR chBuf[BUFSIZE] = { 0 };   

  •   if (!CloseHandle(hChildStdoutWr))   
  •     ErrorExit("Closing handle failed");   

  •   for (;;)   
  •   {
  •     fSuccess = ReadFile( hChildStdoutRdDup, chBuf, BUFSIZE, &dwRead, NULL) ;
  •     if( !fSuccess || dwRead == 0)   
  •     {
  •       break;   
  •     }
  •     fSuccess = WriteFile( hOutputFile, chBuf, dwRead, &dwWritten, NULL) ;
  •     if ( !fSuccess )   
  •       break;   
  •   }

  •   CloseHandle ( hOutputFile ) ;
  • }
  • VOID ErrorExit (LPTSTR lpszMessage)   
  • {
  •   MessageBox( 0, lpszMessage, 0, 0 );
  • }

二、命名管道
命名管道具有以下几个特征:
(1)命名管道是双向的,所以两个进程可以通过同一管道进行交互。
(2)命名管道不但可以面向字节流,还可以面向消息,所以读取进程可以读取写进程发送的不同长度的消息。
(3)多个独立的管道实例可以用一个名称来命名。例如几个客户端可以使用名称相同的管道与同一个服务器进行并发通信。
(4)命名管道可以用于网络间两个进程的通信,而其实现的过程与本地进程通信完全一致。
实验目标:在客户端输入数据a和b,然后发送到服务器并计算a+b,然后把计算结果发送到客户端。可以多个客户端与同一个服务器并行通信。
界面设计:
DSC0001.gif
难点所在:
实现的过程比较简单,但有一个难点。原本当服务端使用ConnectNamedPipe函数后,如果有客户端连接,就可以直接进行交互。原来我在实现过程中,当管道空闲时,管道的线程函数会无限(INFINITE)阻塞。若现在需要停止服务,就必须结束所有的线程,TernimateThread可以作为一个结束线程的方法,但我基本不用这个函数。一旦使用这个函数之后,目标线程就会立即结束,但如果此时的目标线程正在操作互斥资源、内核调用、或者是操作共享DLL的全局变量,可能会出现互斥资源无法释放、内核异常等现象。这里我用重叠I/0来解决这个问题,在创建PIPE时使用FILE_FLAG_OVERLAPPED标志,这样使用ConnectNamedPipe后会立即返回,但线程的阻塞由等待函数WaitForSingleObject来实现,等待OVERLAPPED结构的事件对象被设置。
客户端主要代码:


代码:



[cpp] view plain copy

  • void CMyDlg::OnSubmit()   
  • {
  •   // 打开管道  
  •   HANDLE hPipe = CreateFile("////.//Pipe//NamedPipe", GENERIC_READ | GENERIC_WRITE, /  
  •     0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) ;
  •   if ( hPipe == INVALID_HANDLE_VALUE )  
  •   {
  •     this->MessageBox ( "打开管道失败,服务器尚未启动,或者客户端数量过多" ) ;  
  •     return ;  
  •   }

  •   DWORD nReadByte, nWriteByte ;  
  •   char szBuf[1024] = {0} ;  
  •   // 把两个整数(a,b)格式化为字符串  
  •   sprintf ( szBuf, "%d %d", this->nFirst, this->nSecond ) ;  
  •   // 把数据写入管道  
  •   WriteFile ( hPipe, szBuf, strlen(szBuf), &nWriteByte, NULL ) ;

  •   memset ( szBuf, 0, sizeof(szBuf) ) ;  
  •   // 读取服务器的反馈信息  
  •   ReadFile ( hPipe, szBuf, 1024, &nReadByte, NULL ) ;
  •   // 把返回信息格式化为整数  
  •   sscanf ( szBuf, "%d", &(this->nResValue) ) ;  
  •   this->UpdateData ( false ) ;  
  •   CloseHandle ( hPipe ) ;
  • }

服务端主要代码:


代码:



[cpp] view plain copy

  • // 启动服务  
  • void CMyDlg::OnStart()   
  • {
  •   CString lpPipeName = "////.//Pipe//NamedPipe" ;  
  •   for ( UINT i = 0; i < nMaxConn; i++ )  
  •   {
  •     // 创建管道实例  
  •     PipeInst.hPipe =  CreateNamedPipe ( lpPipeName, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, /
  •           PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, nMaxConn, 0, 0, 1000, NULL ) ;
  •     if ( PipeInst.hPipe == INVALID_HANDLE_VALUE )  
  •     {
  •       DWORD dwErrorCode = GetLastError () ;  
  •       this->MessageBox ( "创建管道错误!" ) ;  
  •       return ;  
  •     }
  •     // 为每个管道实例创建一个事件对象,用于实现重叠IO  
  •     PipeInst.hEvent  =  CreateEvent ( NULL, false, false, false ) ;  
  •     // 为每个管道实例分配一个线程,用于响应客户端的请求  
  •     PipeInst.hTread = AfxBeginThread ( ServerThread, &PipeInst, THREAD_PRIORITY_NORMAL ) ;
  •   }

  •   this->SetWindowText ( "命名管道实例之服务器(运行)" ) ;  
  •   this->MessageBox ( "服务启动成功" ) ;  
  • }
  • // 停止服务  
  • void CMyDlg::OnStop()   
  • {
  •   DWORD dwNewMode = PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_NOWAIT ;  
  •   for ( UINT i = 0; i < nMaxConn; i++ )  
  •   {
  •     SetEvent ( PipeInst.hEvent ) ;
  •     CloseHandle ( PipeInst.hTread ) ;
  •     CloseHandle ( PipeInst.hPipe ) ;
  •   }

  •   this->SetWindowText ( "命名管道实例之服务器" ) ;  
  •   this->MessageBox ( "停止启动成功" ) ;  
  • }

  • // 线程服务函数  
  • UINT ServerThread ( LPVOID lpParameter )  
  • {
  •   DWORD  nReadByte = 0, nWriteByte = 0, dwByte = 0 ;   
  •   char  szBuf[MAX_BUFFER_SIZE] = {0} ;  
  •   PIPE_INSTRUCT  CurPipeInst = *(PIPE_INSTRUCT*)lpParameter ;
  •   OVERLAPPED OverLapStruct = { 0, 0, 0, 0, CurPipeInst.hEvent } ;
  •   while ( true )  
  •   {
  •     memset ( szBuf, 0, sizeof(szBuf) ) ;   
  •     // 命名管道的连接函数,等待客户端的连接(只针对NT)  
  •     ConnectNamedPipe ( CurPipeInst.hPipe, &OverLapStruct ) ;
  •     // 实现重叠I/0,等待OVERLAPPED结构的事件对象  
  •     WaitForSingleObject ( CurPipeInst.hEvent, INFINITE ) ;
  •     // 检测I/0是否已经完成,如果未完成,意味着该事件对象是人工设置,即服务需要停止  
  •     if ( !GetOverlappedResult ( CurPipeInst.hPipe, &OverLapStruct, &dwByte, true ) )  
  •       break ;  

  •     // 从管道中读取客户端的请求信息  
  •     if ( !ReadFile ( CurPipeInst.hPipe, szBuf, MAX_BUFFER_SIZE, &nReadByte, NULL ) )  
  •     {
  •       MessageBox ( 0, "读取管道错误!", 0, 0 ) ;  
  •       break ;  
  •     }

  •     int a, b ;  
  •     sscanf ( szBuf, "%d %d", &a, &b ) ;  
  •     pMyDlg->nFirst    = a ;
  •     pMyDlg->nSecond    = b ;
  •     pMyDlg->nResValue  = a + b ;
  •     memset ( szBuf, 0, sizeof(szBuf) ) ;  
  •     sprintf ( szBuf, "%d", pMyDlg->nResValue ) ;  
  •     // 把反馈信息写入管道  
  •     WriteFile ( CurPipeInst.hPipe, szBuf, strlen(szBuf), &nWriteByte, NULL ) ;
  •     pMyDlg->SetDlgItemInt ( IDC_FIRST, a, true ) ;  
  •     pMyDlg->SetDlgItemInt ( IDC_SECOND, b, true ) ;  
  •     pMyDlg->SetDlgItemInt ( IDC_RESULT, pMyDlg->nResValue, true ) ;  
  •     // 断开客户端的连接,以便等待下一客户的到来  
  •     DisconnectNamedPipe ( CurPipeInst.hPipe ) ;
  •   }

  •   return 0 ;  
  • }

最后特别说明一下,此文章是看雪WIN32安全编程板块的斑竹北极星的,大家可以多多关注一下他的技术文章,看了几篇都认为很不错的。
链 接: http://bbs.pediy.com/showthread.php?t=26252
http://blog.csdn.net/yiruirui0507/article/details/6457806

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-388966-1-1.html 上篇帖子: Windows同时安装Python2和Python3 下篇帖子: Tensorflow Windows Build with GPU Support
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表