进程间的通信
位于同一个进程中的多个线程共享同一个地址空间,因此线程之间的通信非常简单。然而,由于每个进程所拥有的4GB地址空间都是私有的,一个进程不能访问另一个进程地址空间中的数据,因此进程间的通信就比较困难。SOCKET也是进程间通信的一种方式(详见http://blog.csdn.net/walkerkalr/article/details/19443921)。本文将讲述进程间通信的其他方式(剪贴板,匿名管道,命名管道,油槽)。
1.剪贴板
剪贴板实际上是系统维护管理的一块内存区域,当在一个进程中复制数据时,是将这个数据放到该块内存区域中,当在另一个进程中黏贴数据时,是从该内存块区域中取出数据,然后显示在窗口上。
编写利用剪贴板通信的程序:
1)新建一个基于对话框的应用程序,工程名取为Clipboard
2)设计对话框界面
编辑框IDC_EDIT_SEND
编辑框IDC_EDIT_RECV
按钮IDC_BTN_SEND
按钮IDC_BTN_RECV
3)数据发送程序块设计
1>在把数据放置到剪贴板之前,首先需要打开剪贴板,这可以利用CWnd类的OpenClipBorad成员函数实现,该函数的原型声明如下:
BOOL OpenClipboard( );
如果某个程序打开了剪贴板,则其他程序将不能修改剪贴板,直到前者调用了BOOL CloseClipboar(),并且只有调用了BOOL EmptyClipboard(void)函数清空剪贴板,并释放剪贴板中数据的句柄,然后剪贴板的所有权分配给当前打开剪贴板的窗口。
2>放置数据
HANDLE SetClipboardData(
UINT uFormat,
HANDLE hMem);
3>数据发送
void CClipboardDlg::OnBtnSend() { // TODO: Add your control notification handler code here if(OpenClipboard()) { CString str; HANDLE hClip; char *pBuf; EmptyClipboard(); GetDlgItemText(IDC_EDIT_SEND,str); hClip=GlobalAlloc(GMEM_MOVEABLE,str.GetLength()+1); pBuf=(char*)GlobalLock(hClip); strcpy(pBuf,str); GlobalUnlock(hClip); SetClipboardData(CF_TEXT,hClip); CloseClipboard(); } }4>数据接受
void CClipboardDlg::OnBnClickedBtnRecv() { // TODO: Add your control notification handler code here 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(); } } }
2.2父进程的实现:
1)创建单文档类型MFC应用程序
2)创建菜单一个名为“匿名管道”子菜单,添加三个菜单项,并分别为他们添加命令函数,如下
命名相应函数:
void CParentView::OnPipeCreaate()
{
//TODO: Add your command handler code here
}
void CParentView::OnPipeRead()
{
//TODO: Add your command handler code here
}
void CParentView::OnPipeWrite()
{
//TODO: Add your command handler code here
}
3)为ParentView类:添加两个成员变量
private:
HANDLEhWrite;
HANDLEhRead;
并在构造和析构函数做如下操作:
CParentView::CParentView() { //TODO: add construction code here hRead= NULL; hWrite= NULL; } CParentView::~CParentView() { if(hRead) CloseHandle(hRead); if(hWrite) CloseHandle(hrite); }
4)创建匿名管道
void CParentView::OnPipeCreaate() { //TODO: Add your command handler code here SECURITY_ATTRIBUTESsa; sa.bInheritHandle=TRUE; sa.lpSecurityDescriptor=NULL; sa.nLength=sizeof(SECURITY_ATTRIBUTES); if(!CreatePipe(&hRead,&hWrite,&sa,0)) { MessageBox("创建匿名管道失败!"); return; } STARTUPINFOsui; PROCESS_INFORMATIONpi; ZeroMemory(&sui,sizeof(STARTUPINFO)); sui.cb=sizeof(STARTUPINFO); sui.dwFlags=STARTF_USESTDHANDLES; sui.hStdInput=hRead; sui.hStdOutput=hWrite; sui.hStdError=GetStdHandle(STD_ERROR_HANDLE); if(!CreateProcess("..\\Child\\Debug\\Child.exe",NULL,NULL,NULL, TRUE,0,NULL,NULL,&sui,&pi)) { 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 charbuf[100]; DWORDdwRead; if(!ReadFile(hRead,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); return; } MessageBox(buf); } void CParentView::OnPipeWrite() { //TODO: Add your command handler code here charbuf[]="http://www.sunxin.org"; DWORDdwWrite; if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); return; } }以上就是利用匿名管道实现父子进程间通信的父进程程序的实现,在父进程中创建匿名管道,返回该管道的读、写句柄,然后调用CreateProcess函数启动子进程,并且将子进程的标准输入和标准输出句柄设置为匿名管道的读、写句柄,相当于将该管道的读写句柄作了一个标记,传递给子进程,然后在子进程中得到自己的标准输入和标准输出句柄时,相当于得到了匿名管道的读,写句柄。
2.3子进程实现
1)创建一个名为Child的单文档MFC应用程序
2)添加菜单项(不添加创建进程函数)和响应函数
3)添加成员变量,并在构造函数和析构函数中做响应处理
4)获得管道的读取和写入句柄
void CChildView::OnInitialUpdate()
{
// TODO: Addyour specialized code here and/or call the base class
CView::OnInitialUpdate();
hRead= GetStdHandle(STD_INPUT_HANDLE);
hWrite= GetStdHandle(STD_OUTPUT_HANDLE);
}
5)读取数据
void CChildView::OnPipeRead() { //TODO: Add your command handler code here charbuf[100]; DWORDdwRead; if(!ReadFile(hRead,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); return; } MessageBox(buf); }6)写入数据
void CChildView::OnPipeWrite() { //TODO: Add your command handler code here charbuf[]="匿名管道测试程序"; DWORDdwWrite; if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); return; } }2.4测试
运行父进程,单击创建进程菜单项,在出现的子进程窗口中单击写入数据,再在父窗口中单击读取数据,运行结果如下
以上就是对匿名管道的使用,它主要是用来在父子进程之间进行通信。利用匿名管道实现父子进程间通信时,需要注意一点:因为匿名管道没有名称,所以只能在父进程中调用CreateProcess函数创建子进程时,将管道的读、写句柄传递给子进程。
3.命名管道
命名管道通过网络完成进程间的通信,它屏蔽了底层的网络协议细节。相对于匿名官大,命名管道不仅可以在本机实现两个进程间的通信,还可以跨网络试下两个进程间的通信。相对于Sockets,命名管道不需要编写身份验证的代码。命名管道的服务器和客户机之间的区别在于:服务器是唯一一个有权创建命名管道的进程,也只有它才能接受管道客户机的连接请求。而客户机只能同一个线程的命名管道服务器建立连接。
命名管道提供两种基本通信模式:字节模式和消息模式。在字节模式下,数据以一个连续的字节流的形式在客户机和服务器之间流动。而在消息模式下,客户机和服务器则通过一些列不连续的数据单位,进行数据收发,每次在管道上发出一条消息后,它必须作为一条完整的消息读入。
相关函数:
HANDLE CreateNamedPipe( //创建命名管道
LPCTSTR lpName, // pipe name
DWORD dwOpenMode, // pipe open mode
DWORD dwPipeMode, // pipe-specificmodes
DWORD nMaxInstances, // maximum number ofinstances
DWORD nOutBufferSize, // output buffer size
DWORD nInBufferSize, // input buffer size
DWORD nDefaultTimeOut, // time-out interval
LPSECURITY_ATTRIBUTES lpSecurityAttributes // SD
);
3.1服务器端程序
1)创建单文档MFC应用程序
2)添加菜单项
3)增加成员变量及其在构造函数和析构函数中相应的处理
4)创建命名管道
void CNamedPipeSrvView::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; } HANDLEhEvent; hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); if(!hEvent) { MessageBox("创建事件对象失败!"); CloseHandle(hPipe); hPipe=NULL; return; } OVERLAPPEDovlap; ZeroMemory(&ovlap,sizeof(OVERLAPPED)); ovlap.hEvent=hEvent; if(!ConnectNamedPipe(hPipe,&ovlap)) { if(ERROR_IO_PENDING!=GetLastError()) { MessageBox("等待客户端连接失败!"); CloseHandle(hPipe); CloseHandle(hEvent); hPipe=NULL; return; } } if(WAIT_FAILED==WaitForSingleObject(hEvent,INFINITE)) { MessageBox("等待对象失败!"); CloseHandle(hPipe); CloseHandle(hEvent); hPipe=NULL; return; } CloseHandle(hEvent); }
void CNamedPipeSrvView::OnPipeRead() { // TODO:Add your command handler code here charbuf[100]; DWORDdwRead; if(!ReadFile(hPipe,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); return; } MessageBox(buf); }6)写入数据
void CNamedPipeSrvView::OnPipeWrite() { // TODO:Add your command handler code here char buf[]="http://www.sunxin.org"; DWORDdwWrite; if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); return; } } 3.2客户端程序 1)创建单文档MFC应用程序 2)添加菜单项 3)增加成员变量及其在构造函数和析构函数中相应的处理 4)连接命名管道 void CNamedPipeCltView::OnPipeConnect() { // TODO:Add your command handler code here if(!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; } }5)读取数据
void CNamedPipeCltView::OnPipeRead() { // TODO:Add your command handler code here charbuf[100]; DWORDdwRead; if(!ReadFile(hPipe,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); return; } MessageBox(buf); }6)写入数据
void CNamedPipeCltView::OnPipeWrite() { // TODO:Add your command handler code here charbuf[]="命名管道测试程序"; DWORDdwWrite; if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); return; } }7)测试如下
以上就是采用命名管道完成进程通信的实现,具体的过程是:在服务器端调用CreateNamePipe创建命名管道之后,调用ConnectNamedPipe函数让服务器端进程等待客户端进程连接到该命名管道的实例上。在客户端,首先调用WaitNamedPipe函数判断当前是否有可以利用的命名管道,如果有,就调用Create函数打开该命名管道的实例,并建立一个连接。
4.邮槽
邮槽是基于广播体系设计出来的,它采用无连接的不可靠的数据连接。邮槽是一种单向通信机制,创建邮槽的服务器进程读取数据,打开邮槽的客户机进程写入数据。邮槽是基于广播通信的。
HANDLE CreateMailslot(
LPCTSTR lpName, // mailslot name
DWORD nMaxMessageSize, // maximum message size
DWORD lReadTimeout, // read time-outinterval
LPSECURITY_ATTRIBUTES lpSecurityAttributes // inheritanceoption
);
服务器端程序:
voidCMailsotSrv1View::OnMailslotrecv() { // TODO:Add your command handler code here HANDLEhMailslot; hMailslot=CreateMailslot("\\\\.\\mailslot\\MyMailslot",0, MAILSLOT_WAIT_FOREVER,NULL); if(INVALID_HANDLE_VALUE==hMailslot) { MessageBox("创建油槽失败!"); return; } charbuf[100]; DWORDdwRead; if(!ReadFile(hMailslot,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); CloseHandle(hMailslot); return; } MessageBox(buf); CloseHandle(hMailslot); }
客户端程序:
void CMailslotClt1View::OnMailslotSend() { // TODO:Add your command handler code here HANDLEhMailslot; hMailslot=CreateFile("\\\\.\\mailslot\\MyMailslot",GENERIC_WRITE, FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE==hMailslot) { MessageBox("打开油槽失败!"); return; } charbuf[]="http://www.sunxin.org"; DWORDdwWrite; if(!WriteFile(hMailslot,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); CloseHandle(hMailslot); return; } CloseHandle(hMailslot); }测试:
==参考 VC++深入详解
==转载请注明出处,谢谢!
原文:http://blog.csdn.net/walkerkalr/article/details/19674721