VC++深入详解·chapter17·笔记

来源:互联网 发布:淘宝买的机票怎么取 编辑:程序博客网 时间:2024/06/06 14:15

1、介绍进程间通信的四种方式:剪贴板、匿名管道、命名管道、邮槽

2、使用剪贴板实现进程间通信
   发送数据到剪贴板:
   void CClipboardDlg::OnBnClickedBtnSend()
   {
       // TODO: 在此添加控件通知处理程序代码
       if(OpenClipboard())
       {
           CString strSend;//保存发送编辑框控件上的数据
           HANDLE hClip;//保存调用GlobalAlloc函数后分配的内存对象的句柄
           char* pBuf;//保存调用GlobalLock函数后返回的内存地址
           EmptyClipboard();//清空剪贴板上的数据,使当前窗口拥有剪贴板
           GetDlgItemText(IDC_EDIT_SEND, strSend);
           hClip=GlobalAlloc(GMEM_MOVEABLE, strSend.GetLength()+1);
           pBuf=(char* )GlobalLock(hClip);//将内存句柄转化为一个指针
           strcpy(pBuf, strSend.GetBuffer());
           GlobalUnlock(hClip);
           SetClipboardData(CF_TEXT, hClip);
           CloseClipboard();//关闭剪贴板,一定记得此条语句的调用
       }
   }
   从剪贴板接收数据:
   void CClipboardDlg::OnBnClickedBtnRecv()
   {
       // TODO: 在此添加控件通知处理程序代码
       if(OpenClipboard())
       {
           if(IsClipboardFormatAvailable(CF_TEXT))
           {
               HANDLE hClip;
               char* pBuf;
               hClip=GetClipboardData(CF_TEXT);
               pBuf=(char* )GlobalLock(hClip);
               GlobalUnlock(hClip);
               SetDlgItemText(IDC_EDIT_RECV, pBuf);

           }
           CloseClipboard();
       }
   }
   注:剪贴板是系统提供的,所有进程都可以访问它,所以可以采用剪贴板作为进程间通信的一种方式,并且采用这种方式实现进程间的通信,代码的编写比较简单。

3、GetStdHandle函数:该函数可以获得标准输入、标准输出或一个标准错误句柄。
   原型:HANDLE WINAPI GetStdHandle(__in DWORD nStdHandle);
   Retrieves a handle to the specified standard device (standard input, standard output, or standard error).
   参数取值:STD_INPUT_HANDLE   STD_OUTPUT_HANDLE   STD_ERROR_HANDLE
   If the function fails, the return value is INVALID_HANDLE_VALUE.

4、ZeroMemory函数:::ZeroMemory(&sui, sizeof(STARTUPINFOW));
   Fills a block of memory with zeros.
   To avoid any undesired effects of optimizing compilers, use the SecureZeroMemory function.
   原型:void ZeroMemory([in] PVOID Destination,[in] SIZE_T Length);
   Parameters
   Destination [in]
   A pointer to the starting address of the block of memory to fill with zeros.
   Length [in]
   The size of the block of memory to fill with zeros, in bytes.

5、匿名管道:
   1> 匿名管道是一个未命名的、单向管道,通常用来在一个父进程和一个子进程之间传输数据。匿名管道只能实现本地机器上两个进程间的通信,而不能实现跨网的通信。
   2> CreatePipe函数:创建一个匿名管道,返回该匿名管道的读写句柄
   3> CreateProcess函数:启动一个进程,注意完整的路径设置
   4> 从管道读取数据:ReadFile函数
   5> 从管道写入数据:WriteFile函数
   6> 为了利用父进程创建的匿名管道进行通信,子进程中,首先就要得到子进程的标准输入和输出句柄,这可以在其view类窗口完全创建成功后去获取。即在重载的OnInitialUpdate函数中利用 GetStdHandle函数进行获取
   7> OnInitialUpdate函数(override):这个函数是谠窗口成功创建之后,第一个调用的函数
   8> 利用匿名管道通信时,子进程必须是由父进程来启动的
   9> 另外,利用匿名管道还可以实现只在同一进程内读取和写入数据。当然,主要它是用来在父子进程间进行通信
   10> 因为匿名管道没有名称,所以只能在父进程中调用CreateProcess函数创建子进程时,将管道的读、写句柄传递给子进程
   eg:代码
   view类添加数据成员:HANDLE hRead; HANDLE hWrite;
   在构造函数中:hRead=NULL; hWrite=NULL;
   在析构函数中:if(hRead) CloseHandle(hRead);   if(hWrite) CloseHandle(hWrite);
   创建匿名管道(父进程中)
   void CUnnamedPipeView::OnPipeCreate()
   {
       // TODO: 在此添加命令处理程序代码
       SECURITY_ATTRIBUTES sa;
       sa.bInheritHandle=TRUE;
       sa.lpSecurityDescriptor=NULL;
       sa.nLength=sizeof(SECURITY_ATTRIBUTES);
       if(!CreatePipe(&hRead, &hWrite, &sa, 0))
       {
           MessageBox(_T("创建匿名管道失败!"));
           return;
       }

       PROCESS_INFORMATION pi;
       STARTUPINFOW sui;
       ::ZeroMemory(&sui, sizeof(STARTUPINFOW));
       sui.cb=sizeof(STARTUPINFOW);
       sui.dwFlags=STARTF_USESTDHANDLES;
       sui.hStdInput=hRead; //将父进程创建匿名管道时返回的读句柄赋给所启动子进程的标准输入句柄
       sui.hStdOutput=hWrite; //将父进程创建匿名管道时返回的写句柄赋给所启动子进程的标准输出句柄
       sui.hStdError=GetStdHandle(STD_ERROR_HANDLE);
       if(!CreateProcess(_T("F://F-disk//C++//CPP//VC//PipeChile//debug//PipeChile.exe"), NULL, NULL, NULL, TRUE, 0,
               NULL, NULL, &sui, &pi)) //启动子进程PipeChile.exe,同时提供了一种在一个进程中打开另一个进程的方法
       {
           CloseHandle(hRead);
           CloseHandle(hWrite);
           hRead=NULL;
           hWrite=NULL;
           MessageBox(_T("子进程启动失败!"));
           return ;
       }
       else
       {
           CloseHandle(pi.hProcess);
           CloseHandle(pi.hThread);
       }
   }
   从匿名管道读取数据和写入数据:(父进程)
   void CUnnamedPipeView::OnPipeRead()
   {
       // TODO: 在此添加命令处理程序代码
       char buf[100];
       DWORD dwRead;
       if(!ReadFile(hRead, buf, 100, &dwRead, NULL))
       {
           MessageBox(_T("读取数据失败!"));
           return ;
       }
       MessageBox(CString(buf));

   }
   void CUnnamedPipeView::OnPipeWrite()
   {
       // TODO: 在此添加命令处理程序代码
       char buf[ ]="http://hi.baidu.com";
       DWORD dwWrite;
       if(!WriteFile(hWrite, buf, strlen(buf)+1, &dwWrite, NULL))
       {
           MessageBox(_T("写入数据失败!"));
           return ;
       }
   }
   子进程中初始化读写句柄(父进程句柄的传递):
   void CPipeChileView::OnInitialUpdate()
   {
       CView::OnInitialUpdate();

       // TODO: 在此添加专用代码和/或调用基类
       hRead=GetStdHandle(STD_INPUT_HANDLE);
       hWrite=GetStdHandle(STD_OUTPUT_HANDLE);
   }
   从匿名管道读取数据和写入数据:(子进程)
   void CPipeChileView::OnPipeRead()
   {
       // TODO: 在此添加命令处理程序代码
       char buf[100];
       DWORD dwRead;
       if(!ReadFile(hRead, buf, 100, &dwRead, NULL))
       {
           MessageBox(_T("数据读取失败!"));
           return;
       }
       MessageBox(CString(buf));
   }
   void CPipeChileView::OnPipeWrite()
   {
       // TODO: 在此添加命令处理程序代码
       char buf[]="匿名管道测试程序";
       DWORD dwWrite;
       if(!WriteFile(hWrite, buf, strlen(buf)+1, &dwWrite, NULL))
       {
           MessageBox(_T("数据写入失败!"));
           return ;
       }
   }

6、命名管道
   1> 命名管道通过网络来完成进程间的通信,它屏蔽了底层的网络协议细节。我们在不了解网络协议的情况下,也可以利用命名管道来实现进程间的通信。匿名管道哦只能在本地机器上的父子进程间进行通信,而命名管道不仅可以在本机上实现两个进程间的通信,还可以跨网络实现两个进程间的通信。
       命名管道作为一种网路编程方案时,它实际上建立了一个客户机/服务器通信机系,并在其中可靠地传输数据。命名管道服务器和客户机的区别在于:服务器是惟一一个有权创建命名管道的进程,也只有它才能接受管道客户机的连接请求。而客户机只能同一个现成的命名管道服务器建立连接。
       命名管道提供了两种基本通信模式:字节模式和消息模式。
   2> 相关函数:
       CreateNamedPipe函数:创建一个命名管道实例,并返回该命名管道的句柄。
       ConnectNamedPipe函数:让服务器等待客户端的连接请求的到来。
       WaitNamedPipe函数:判断是否有可以利用的命名管道
   3> 命名管道不能实现同一进程内部的通信(读取和写入)
   4> 代码
   view类添加数据成员:HANDLE hPipe;
   在构造函数中:hPipe=NULL;
   在析构函数中:if(hPipe) CloseHandle(hPipe);
   创建命名管道(服务器端):
   void CNamedPipe_ParentView::OnPipeCreate()
   {
       // TODO: 在此添加命令处理程序代码
       hPipe=CreateNamedPipe(_T("////.//pipe//Mypipe"),PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0, 1,
                               1024, 1024, 0, NULL);
       if(INVALID_HANDLE_VALUE==hPipe)
       {
           MessageBox(_T("命名管道创建失败!"));
           return ;
       }
       //创建匿名的人工重置的事件对象
       HANDLE hEvent;
       hEvent=CreateEvent(NULL, TRUE, FALSE, NULL);
       if(!hEvent)
       {
           MessageBox(_T("创建事件对象失败!"));
           CloseHandle(hPipe);
           hPipe=NULL;
           return ;
       }
       OVERLAPPED ovlap;
       ::ZeroMemory(&ovlap, sizeof(OVERLAPPED));
       ovlap.hEvent=hEvent;
       //等待客户端请求的到来
       if(!ConnectNamedPipe(hPipe, &ovlap))
       {
           if(ERROR_IO_PENDING!=GetLastError())
           {
               MessageBox(_T("等待对象失败!"));
               CloseHandle(hPipe);
               CloseHandle(hEvent);
               hPipe=NULL;
               return ;
           }
       }
       //等待事件对象变为有信号状态,即等待有一个客户端链接倒命名管道的实例上
       if(WAIT_FAILED==WaitForSingleObject(hEvent, INFINITE))
       {
           MessageBox(_T("等待对象失败!"));
           CloseHandle(hPipe);
           CloseHandle(hEvent);
           hPipe=NULL;
           return ;
       }
       CloseHandle(hEvent);
   }
   //读取数据(服务器端/客户端)
   void CNamedPipe_ParentView::OnPipeRead()
   {
       // TODO: 在此添加命令处理程序代码
       char buf[100];
       DWORD dwRead;
       if(!ReadFile(hPipe, buf, 100, &dwRead, NULL))
       {
           MessageBox(_T("数据读取失败!"));
           return ;
       }
       MessageBox(CString(buf));
   }
   //写入数据(服务器端/客户端)
   void CNamedPipe_ParentView::OnPipeWrite()
   {
       // TODO: 在此添加命令处理程序代码
       char buf[]="http://hi.baidu.com";
       DWORD dwWrite;
       if(!WriteFile(hPipe, buf, strlen(buf)+1, &dwWrite, NULL))
       {
           MessageBox(_T("数据写入失败!"));
           return ;
       }
   }
   连接命名管道(客户端):
   void CNamedPipe_ClientView::OnPipeConnect()
   {
       // TODO: 在此添加命令处理程序代码
       if(!WaitNamedPipe(_T("////.//pipe//Mypipe"), NMPWAIT_WAIT_FOREVER))
       {
           MessageBox(_T("当前没有可利用的命名管道实例!"));
           return ;
       }
       //打开可用的命名管道,并与服务器端进程进行通信
       hPipe=CreateFile(_T("////.//pipe//Mypipe"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
       if(INVALID_HANDLE_VALUE==hPipe)
       {
           MessageBox(_T("打开命名管道失败!"));
           hPipe=NULL;
           return ;
       }
   }
   利用命名管道完成进程间通信的实现的具体过程:在服务器端调用CreateNamedPipe创建命名管道之后,调用 ConnectNamedPipe函数让服务器端进程等待客户端进程连接倒该命名管道的实力上。在客户端,首先调用WaitNamedPipe函数判断当前是否有可以利用的命名管道实例,如果有,就调用CreateFile函数打开命名管道实例,并建立一个连接。

7、邮槽
   1> 邮槽是基于广播通信体系设计出来的,它采用无连接的不可靠的数据传输。邮槽是一种单向通信机制,创建邮槽的服务器进程读取数据,打开邮槽的客户机进程写入数据。
       我们在传输消息的时候,应将消息的长度限制在424字节以下。
   2> CreateMailslot函数:该函数利用指定的名称创建一个邮槽,然后返回所创建的邮槽的句柄。
   3> 对邮槽服务器端进程来说,它只能接收数据,所以如果邮槽创建成功,那么就可以直接调用ReadFile函数从邮槽读取并现实数据了。
   4> 代码:
   //服务器端接收数据
   void CMailSlot_ServerView::OnMailslotRecv()
   {
       // TODO: 在此添加命令处理程序代码
       hMailslot=CreateMailslot(_T("////.//mailslot//MyMailslot"), 0, MAILSLOT_WAIT_FOREVER, NULL);
       if(INVALID_HANDLE_VALUE==hMailslot)
       {
           MessageBox(_T("邮槽创建失败!"));
           return;
       }
       //读取数据
       char buf[100];
       DWORD dwRead;
       if(!ReadFile(hMailslot, buf, 100, &dwRead, NULL))
       {
           MessageBox(_T("读取数据失败!"));
           return ;
       }
       MessageBox(CString(buf));
       CloseHandle(hMailslot);
   }
   //客户端发送数据
   void CMailSlot_ClientView::OnMailslotSend()
   {
       // TODO: 在此添加命令处理程序代码
       hMailslot=CreateFile(_T("////.//mailslot//MyMailslot"), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
       if(INVALID_HANDLE_VALUE==hMailslot)
       {
           MessageBox(_T("打开油槽失败!"));
           return ;
       }
       char buf[ ]="http://hi.baidu.com";
       DWORD dwWrite;
       if(!WriteFile(hMailslot, buf, strlen(buf)+1, &dwWrite, NULL))
       {
           MessageBox(_T("写入数据失败!"));
           CloseHandle(hMailslot);
           return ;
       }
       CloseHandle(hMailslot);

   }
   5> 若想实现在同一程序中通过邮槽接收和发送数据,可以在同一程序中同时实现邮槽的服务器端(即读取端)和客户端(即发送端),利用前者接收数据,后者发送数据。
   6> 邮槽的是基于广播通信的,可以实现一对多的单向通信,我们可以利用邮槽的之一特性编写一个网络会议通知系统,而且实现这样的代码非常少。(一个客户端,n 个服务区端)

8、本章小结:本章主要介绍了四种进程间通信的方式,其中剪贴板和匿名管道只能实现统一太机器上两个进程的通信,而不能实现跨网络的通信
;而命名管道和邮槽不仅可以实现同一台机器上两个进程的通信,还可以实现跨网络的进程间通信;另外,邮槽还可以实现一对多通信,而命名管道只能是点对点的单一的通信,但邮槽的缺点是数据量较小,通常都是在424BYTES以下,如果数据量较大,则可以采用命名管道的方式来完成。这四种方式各有优缺点,在实际应用中应根据具体情况选用合适的方式。

原创粉丝点击