孙鑫VC学习(第17课--进程间通信)

来源:互联网 发布:加尔默罗修女知乎 编辑:程序博客网 时间:2024/06/14 01:50

进程间通信的四种方式

n      剪贴板

n      匿名管道

n      命名管道

n      邮槽

剪切板是将数据放到内存区域中,然后再取出来。

剪切板程序:(基于对话框的程序,利用剪切板进行通信的例子):

两个EDIT BOX,一个是发送数据,一个是接收数据,再放置两个按钮,一个是发送,一个是接受,当按下发送按钮,数据放到剪切板,接受按钮取出数据并显示。(可以把发送和接受的代码放到不同的进程中进行通信。)

void CClipBoardDlg::OnBtnSend() //将数据送到剪切板里面

{

      // TODO: Add your control notification handler code here

      if (OpenClipboard( ))

      {

             CString str;

             EmptyClipboard();

             GetDlgItemText(IDC_EDIT_SEND,str);

             HANDLE hMem=GlobalAlloc(GMEM_MOVEABLE,str.GetLength()+1);//str.GetLength()+1多分配一个空字节

             char *pBuf=(char*)GlobalLock(hMem);

             strcpy(pBuf,str);

             GlobalUnlock(hMem);

             SetClipboardData(CF_TEXT,hMem);

             CloseClipboard();

      }

}

 

void CClipBoardDlg::OnBtnRecv()

{

      // TODO: Add your control notification handler code here

      if (OpenClipboard( ))

      {

             if (IsClipboardFormatAvailable(CF_TEXT))

             {

                    HANDLE hdata=GetClipboardData(CF_TEXT);

                    char *pBuf=(char*)GlobalLock(hdata);

                    GlobalUnlock(hdata);

                    SetDlgItemText(IDC_EDIT_RECV,pBuf);

                    CloseClipboard();

             }

      }

}

 

下面创建一个匿名管道:(匿名管道只能在父子进程间通信)

创建一个单文档的程序(父程序。Parent),写入三个菜单:创建管道,写入数据,读取数据。并在VIEW类中加上命令响应函数。

CParentView增加

private:

      HANDLE hRead;

      HANDLE hWrite;

 

 

CParentView::CParentView()

{

      // TODO: add construction code here

    hRead=NULL;

       hWrite=NULL;

}

 

CParentView::~CParentView()

{

      if (hRead)

      {

             CloseHandle(hRead);

      }

      if (hWrite)

      {

             CloseHandle(hWrite);

      }

}

 

BOOL CreateProcess(

 LPCTSTR lpApplicationName,                 // 若指定文件名,在当前目录下寻找可执行文件名,不会搜索,若没有找到,失败返回。不会自动加上EXE文件名。本例中的"..//Child//Debug//Child.exe",..是指返回到Parent的上一级目录。ParentChild是平级.

 LPTSTR lpCommandLine,                      // 若没加目录,自动加EXE扩展名。自动搜索。

 LPSECURITY_ATTRIBUTES lpProcessAttributes, // 当调用CreateProcess创建新进程时,系统会自动创建进程内核对象和线程内核对象(用于进程主线程)并将它们的使用计数设置为2(创建的时候本身为1,创建时打开再加1,为2,所以后面要关闭),本参数和下一个是用来设定创建的新进程和线程的安全属性,以及指定父进程将来生成的其它线程是否可以继承他们的句柄。若为NULL,为默认的安全属性。

 LPSECURITY_ATTRIBUTES lpThreadAttributes,  // SD

 BOOL bInheritHandles,                      // handle inheritance option

 DWORD dwCreationFlags,                     // creation flags

 LPVOID lpEnvironment,                      // new environment block

 LPCTSTR lpCurrentDirectory,                // current directory name

 LPSTARTUPINFO lpStartupInfo,               // startup information

 LPPROCESS_INFORMATION lpProcessInformation // process information

);

 

void CParentView::OnPipeCreate()

{

      // TODO: Add your command handler code here

      SECURITY_ATTRIBUTES sa;

      sa.bInheritHandle=TRUE;

      sa.lpSecurityDescriptor=NULL;

      sa.nLength=sizeof(SECURITY_ATTRIBUTES);

      if(!CreatePipe(&hRead,&hWrite,&sa,0))

      {

             MessageBox("创建匿名管道失败");

             return ;

      }//如果成功,启动子进程,将读写管道的读写句柄传给子进程。

      STARTUPINFO sp;

      ZeroMemory(&sp,sizeof(STARTUPINFO));

      sp.cb=sizeof(STARTUPINFO);

      sp.dwFlags=STARTF_USESTDHANDLES;

      sp.hStdInput=hRead;

      sp.hStdOutput=hWrite;

      sp.hStdError=GetStdHandle(STD_ERROR_HANDLE);//得到父进程的标准错误句柄。

      PROCESS_INFORMATION pi;

      if(!CreateProcess("..//Child//Debug//Child.exe",NULL,NULL,NULL,true,0,NULL,NULL,&sp,&pi))//启动子进程的函数CreateProcess

      {

             CloseHandle(hRead);

             CloseHandle(hWrite);

             hRead=NULL;

             hWrite=NULL;

             MessageBox("创建子进程失败");

             return ;

      }

      else

      {

             CloseHandle(pi.hProcess);

             CloseHandle(pi.hThread);

      }

 

}

 

void CParentView::OnPipeRead()

{

      // TODO: Add your command handler code here

      char Buf[100];

      DWORD dwread;

      if(!ReadFile(hRead,Buf,100,&dwread,NULL))

      {

             MessageBox("读取数据失败!");

             return;

      }

      MessageBox(Buf);

}

 

void CParentView::OnPipeWrite()

{

      // TODO: Add your command handler code here

      char Buf[]="http://sunxin.org";

      DWORD dwwrite;

      if(!WriteFile(hWrite,Buf,strlen(Buf)+1,&dwwrite,NULL))

      {

             MessageBox("写入数据失败!");

             return;

      }

}

 

创建一个单文档的程序(子程序。Child和父程序同级),写入三个菜单:写入数据,读取数据。并在VIEW类中加上命令响应函数。

首先要得到子进程的标准输入输出句柄。可以在VIEW类的窗口完全创建成功之后获取。

VIEW类增加:

private:

      HANDLE hread;

      HANDLE hwrite;

 

CChildView::CChildView()

{

      // TODO: add construction code here

   hread=NULL;

      hwrite=NULL;

}

 

CChildView::~CChildView()

{

      if (hread)

      {

             CloseHandle(hread);

      }

      if (hwrite)

      {

             CloseHandle(hwrite);

      }

}

 

void CChildView::OnInitialUpdate()

{

      CView::OnInitialUpdate();

      

      // TODO: Add your specialized code here and/or call the base class

      hread=GetStdHandle(STD_INPUT_HANDLE);

      hwrite=GetStdHandle(STD_OUTPUT_HANDLE);

}

 

void CChildView::OnPipeRead()

{

      // TODO: Add your command handler code here

      char Buf[100];

      DWORD dwread;

      if(!ReadFile(hread,Buf,100,&dwread,NULL))

      {

             MessageBox("读取数据失败!");

             return;

      }

      MessageBox(Buf);

}

 

void CChildView::OnPipeWrite()

{

      // TODO: Add your command handler code here

      char Buf[]="this is child";

      DWORD dwwrite;

      if(!WriteFile(hwrite,Buf,strlen(Buf)+1,&dwwrite,NULL))

      {

             MessageBox("写入数据失败!");

             return;

      }

}

 

启动:子进程由父进程启动(本地机器,父子进程间,不可以跨网络)。自己写自己读也是可以的。

 

命名管道。(可以在本地机器,还可以跨网络)

n      命名管道是通过网络来完成进程间的通信,它屏蔽了底层的网络协议细节。我们在不了解网络协议的情况下,也可以利用命名管道来实现进程间的通信。

n      命名管道充分利用了Windows NTWindows 2000内建的安全机制。

n      将命名管道作为一种网络编程方案时,它实际上建立了一个客户机/服务器通信体系,并在其中可靠地传输数据。

n      命名管道是围绕Windows文件系统设计的一种机制,采用命名管道文件系统(Named Pipe File SystemNPFS)”接口,因此,客户机和服务器可利用标准的Win32文件系统函数(例如:ReadFileWriteFile)来进行数据的收发。

n      命名管道服务器和客户机的区别在于:服务器是唯一一个有权创建命名管道的进程,也只有它才能接受管道客户机的连接请求。而客户机只能同一个现成的命名管道服务器建立连接。

n      命名管道服务器只能在Windows NTWindows 2000上创建,所以,我们无法在两台Windows 95Windows 98计算机之间利用管道进行通信。不过,客户机可以是Windows 95Windows 98计算机,与Windows NTWindows 2000计算机进行连接通信。

n      命名管道提供了两种基本通信模式:字节模式和消息模式。在字节模式中,数据以一个连续的字节流的形式,在客户机和服务器之间流动。而在消息模式中,客户机和服务器则通过一系列不连续的数据单位,进行数据的收发,每次在管道上发出了一条消息后,它必须作为一条完整的消息读入。

命名管道可以在创建的时候指定哪组用户可以访问管道,不需要编写用户身份验证的代码。

先编写命名管道的服务器端程序。(单文档,名字是NamePipeSrv)写入三个菜单:创建管道,写入数据,读取数据。并在VIEW类中加上命令响应函数。

VIEW类增加:

private:

      HANDLE hPipe;

CNamePipeSrvView::CNamePipeSrvView()

{

      // TODO: add construction code here

hPipe=0;

}

 

CNamePipeSrvView::~CNamePipeSrvView()

{

      if (hPipe)

      {

             CloseHandle(hPipe);

      }

}

 

void CNamePipeSrvView::OnPipeCreate()

{

      // TODO: Add your command handler code here

      hPipe=CreateNamedPipe("////.//pipe//MyPipe ",PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,

             0,1,1024,1024,0,NULL);

      if (INVALID_HANDLE_VALUE==hPipe)

      {

             MessageBox("创建命名管道失败!");

             hPipe=NULL;

             return;

      }

      HANDLE hevent=CreateEvent(NULL,true,false,NULL);

      if (NULL==hevent)

      {

             MessageBox("创建事件失败!");

             CloseHandle(hPipe);

             hPipe=NULL;

             CloseHandle(hevent);

             return;

      }

      OVERLAPPED  ov;

      ZeroMemory(&ov,sizeof(OVERLAPPED));

      ov.hEvent=hevent;

      if(0==ConnectNamedPipe(hPipe,&ov))

      {

             if(ERROR_IO_PENDING!=GetLastError())//ERROR_IO_PENDING表示

//这个操作是个未决的操作,表示在随后的时间内,会完成。

             {

                    MessageBox("等到客户端连接请求失败!");

                    CloseHandle(hPipe);

                    hPipe=NULL;

                    CloseHandle(hevent);

                    return;

             }

             

      }

      if(WAIT_FAILED==WaitForSingleObject(hevent,INFINITE))

      {

           MessageBox("等到对象失败!");

                    CloseHandle(hPipe);

                    hPipe=NULL;

                    CloseHandle(hevent);

                    return;

      }

      //表示有一个事件实例连接到了命名管道。函数成功。

      CloseHandle(hevent);

}

 

void CNamePipeSrvView::OnPipeRead()

{

      // TODO: Add your command handler code here

      char Buf[100];

      DWORD dwread;

      if(!ReadFile(hPipe,Buf,100,&dwread,NULL))

      {

             MessageBox("读取数据失败!");

             return;

      }

      MessageBox(Buf);

 

}

 

void CNamePipeSrvView::OnPipeWrite()

{

      // TODO: Add your command handler code here

      char Buf[]="this is NamePipeSrv";

      DWORD dwwrite;

      if(!WriteFile(hPipe,Buf,strlen(Buf)+1,&dwwrite,NULL))

      {

             MessageBox("写入数据失败!");

             return;

      }

 

}

 

下面编写管道的客户端程序。(单文档,与服务器平级),增加3个菜单,连接管道,读取数据,写入数据,并在VIEW增加命令响应。

VIEW类增加:

private:

      HANDLE hpipe;

 

CNamePipeClientView::CNamePipeClientView()

{

      // TODO: add construction code here

 hpipe=NULL;

}

 

CNamePipeClientView::~CNamePipeClientView()

{

      if (hpipe)

      {

             CloseHandle(hpipe);

      }

}

 

void CNamePipeClientView::OnPipeLink()

{

      // TODO: Add your command handler code here

      //在连接前,先判断当前是否有可以利用的命名管道实例

      ////servername/pipe/pipename 如果是跨网络通信,要设置servername为通信的机器的主机名

      if(0==WaitNamedPipe("////.//pipe//MyPipe",NMPWAIT_WAIT_FOREVER))

      {

             MessageBox("当前没有可利用的命名管道实例!");

             return;

      }

      hpipe=CreateFile("////.//pipe//MyPipe",GENERIC_READ|GENERIC_WRITE,0, 

 NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

      if(INVALID_HANDLE_VALUE==hpipe)

      {

             MessageBox("打开命名管道失败!");

             hpipe=NULL;

             return;

      }

}

 

void CNamePipeClientView::OnPipeRead()

{

      // TODO: Add your command handler code here

      // TODO: Add your command handler code here

      char Buf[100];

      DWORD dwread;

      if(!ReadFile(hpipe,Buf,100,&dwread,NULL))

      {

             MessageBox("读取数据失败!");

             return;

      }

      MessageBox(Buf);

 

}

 

void CNamePipeClientView::OnPipeWrite()

{

      // TODO: Add your command handler code here

      // TODO: Add your command handler code here

      char Buf[]="this is NamePipeClient";

      DWORD dwwrite;

      if(!WriteFile(hpipe,Buf,strlen(Buf)+1,&dwwrite,NULL))

      {

             MessageBox("写入数据失败!");

             return;

      }

 

}

命名管道的服务器和客户端不用有任何的关系。启动后,服务器端先创建管道,客户端连接管道。然后就可以读写数据了。

 

 

邮槽

n      邮槽是基于广播通信体系设计出来的,它采用无连接的不可靠的数据传输。

n      邮槽是一种单向通信机制,创建邮槽的服务器进程读取数据,打开邮槽的客户机进程写入数据。

n      为保证邮槽在各种Windows平台下都能够正常工作,我们传输消息的时候,应将消息的长度限制在424字节以下。

创建油槽程序:单文档(MailSlotSrv);服务器端只能接收数据,添加一个接收数据的按钮,在VIEW中增加消息响应。

void CMailSlotSrvView::OnRecvData()

{

      // TODO: Add your command handler code here

      HANDLE  hmailslot;

      hmailslot=CreateMailslot("////.//mailslot//MyMailslot",

             0,MAILSLOT_WAIT_FOREVER,NULL );

      if(INVALID_HANDLE_VALUE==hmailslot)

      {

             MessageBox("创建油槽失败!");

             CloseHandle(hmailslot);

             return;

      }

//读取数据

      char Buf[100];

      DWORD dwread;

      if(!ReadFile(hmailslot,Buf,100,&dwread,NULL))

      {

             MessageBox("读取数据失败!");

CloseHandle(hmailslot);

             return;

      }

      MessageBox(Buf);

 

      CloseHandle(hmailslot);

 

}

 

下面是油槽的客户端程序:

MFC的单文档(MailSlotClient)添加写入数据的按钮,添加VIEW的消息响应函数。

void CMailSlotClientView::OnSendData()

{

      // TODO: Add your command handler code here

      //如果不是在本地机器上,////./mailslot//MyMailslot中的.要替换成油槽的服务器端的进程所在的机器名

      HANDLE hslot=CreateFile("////.//mailslot//MyMailslot",

GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);

      if (INVALID_HANDLE_VALUE==hslot)

      {

             MessageBox("打开油槽失败!");

             return;

      }

 

      char Buf[]="this is MailSlotClient";

      DWORD dwwrite;

      if(!WriteFile(hslot,Buf,strlen(Buf)+1,&dwwrite,NULL))

      {

             MessageBox("写入数据失败!");

CloseHandle(hslot);

             return;

      }

 

      CloseHandle(hslot);

 

}

 

剪切板和匿名管道只能实现在一台机器上两个进程间通信,不能跨网络通信。而命名管道和油槽不仅能实现同一台机器上的通信,也可以跨网络实现两个进程通信。油槽可以实现1对多的通信,而命名管道只能实现点到点的单一的通信。利用一个实例通信。油槽通信量比较小,(424字节以下)。

注:
1.在剪切板中取数据时,不必要调用EmptyClipboard()(此函数是让当前进程拥有剪切板)。
2.匿名管道:CreatePipe,CreateProcess然后进行读写,CHILD端:hread=GetStdHandle(STD_INPUT_HANDLE);

hwrite=GetStdHandle(STD_OUTPUT_HANDLE);来进行读写。父子进行通信,父创建管道。不能跨网络,只能在同一台机器上通信。
3.命名管道:可以跨网络,服务器来创建管道,客户端只能和服务器端连接。客户端:CreateNamedPipe,ConnectNamedPipe,

WaitForSingleObject。客户端与服务器端级别无所谓,WaitNamedPipe,CreateFile.
4.注意ERROR_IO_PENDING用法。
5.邮槽:服务器端只能接收数据,客户端只能发送数据。服务器端 CreateMailslot,ReadFile。客户端CreateFile

(FILE_SHARE_READ),WriteFile
6.要记得及时关闭句柄。