进程间的通信
位于同一个进程中的多个线程共享同一个地址空间,因此线程之间的通信非常简单。然而,由于每个进程所拥有的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创建管道和创建进程
匿名管道是一个未命名的、单向管道,通常用来在一个父进程和一个子进程之间传输数据。匿名管道只能实现本地机器上网络的通信,而不能实现跨网络的通信。
BOOL CreatePipe(//创建管道
PHANDLE hReadPipe, // read handle
PHANDLE hWritePipe, // write handle
LPSECURITY_ATTRIBUTES lpPipeAttributes, // security attributes
DWORD nSize // pipe size
);
CreateProcess( //创建进程
LPCWSTR lpszImageName,
LPCWSTR lpszCmdLine,
LPSECURITY_ATTRIBUTES lpsaProcess,
LPSECURITY_ATTRIBUTES lpsaThread,
BOOL fInheritHandles,
DWORD fdwCreate,
LPVOID lpvEnvironment,
LPWSTR lpszCurDir,
LPSTARTUPINFOW lpsiStartInfo,
LPPROCESS_INFORMATION lppiProcInfo);
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);
}
5)读取数据
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++深入详解
==转载请注明出处,谢谢!