进程间通讯的四种方式:剪贴板、匿名管道、命名管道和邮槽
第一种:剪贴板
(1)新建一个基于对话框的应用程序,并设置好如下界面:
(2)分别编辑发送按钮和接收按钮的代码:
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();//关闭剪贴板
}
}
void CClipboardDlg::OnBtnRecv()
{
// TODO: Add your control notification handler code here
if(OpenClipboard())
{
//The IsClipboardFormatAvailable function determines whether the clipboard contains data in the specified format
if(IsClipboardFormatAvailable(CF_TEXT))
{
HANDLE hClip;
char *pBuf;
hClip=GetClipboardData(CF_TEXT);
pBuf=(char *)GlobalLock(hClip);//The GlobalLock function locks a global memory object and returns a pointer to the first byte of the object's memory block
GlobalUnlock(hClip);
SetDlgItemText(IDC_EDIT_RECV,pBuf);
CloseClipboard();
}
}
}
第二种:匿名管道
<1>新建一个基于单文档的工程,工程名为"Parent"
(1)添加如下菜单项,并添加命令响应函数 CChildView::OnPipeRead()、CChildView::OnPipeWrite();
(2)在CParentView类中添加两个句柄
HANDLE hRead 和 HANDLE hWrite,属性设为私有,并在构造函数中进行初始化,在析构函数中释放该句柄
CParentView::CParentView()
{
// TODO: add construction code here
hRead=NULL;
hWrite=NULL;
}
CParentView::~CChildView()
{
if(hRead)
CloseHandle(hRead);
if(hWrite)
CloseHandle(hWrite);
}
(3)编写CParentView::OnPipeCreate() 函数,注意用到两个函数CreatePipe(...)和CreateProcess(...)分别用于创建管道和启动子进程。代码如下:
void CParentView::OnPipeCreate()
{
// TODO: Add your command handler code here
SECURITY_ATTRIBUTES sa;//定义一个安全属性结构图
sa.bInheritHandle=TRUE;//TRUE表示可以被子进程继承
sa.lpSecurityDescriptor=NULL;
sa.nLength=sizeof(SECURITY_ATTRIBUTES);
if(CreatePipe(&hRead,&hWrite,&sa,0))//创建匿名管道
{
MessageBox("匿名管道创建失败!");
return ;
}
//如果创建成功则启动子进程,将匿名管道的读写句柄传递给子进程
STARTUPINFO sui;
PROCESS_INFORMATION pi;
ZeroMemory(&sui,sizeof(STARTUPINFO));//将这个结构体内的所有成员设为0
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);//关闭主进程线程句柄
}
}
(4)编写OnPipeRead()和OnPipeWrite()的具体实现:
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://www.sunxin.org";
DWORD dwWrite;
if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写入数据失败!");
return;
}
}
<2>在该工程中再添加一个项目,同样是基于单文档的,项目名为"Child",于"Parent"保持平级即可。
(1)添加如下菜单项,并添加命令响应函数 CChildView::OnPipeRead()、CChildView::OnPipeWrite();
(2)首先我们要获得子进程的标准输入和标准输出句柄,这个可以在CChildView类的窗口完全创建的时候去获取,此时我们可以用一个CChildView::OnInitialUpdate()的虚函数去获取,OnInitialUpdate()是在窗口完全创建之后第一个执行的函数。
(3)在CChildView类中添加两个句柄 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);
}
(4)在
OnInitialUpdate()函数中通过 GetStdHandle(STD_INPUT_HANDLE)、GetStdHandle(STD_OUTPUT_HANDLE)得到标准输入输出句柄
hRead=GetStdHandle(STD_INPUT_HANDLE);//得到管道的读取句柄
hWrite=GetStdHandle(STD_OUTPUT_HANDLE);//得到管道的写入句柄
(5)接着编写读取进程OnPipeRead()和写入进程OnPipeWrite()代码的具体实现:
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[]="匿名管道测试程序";
DWORD dwWrite;
if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写入数据失败!");
return;
}
}
<1>、<2>都完成之后就是进行测试了, 注意:对于匿名管道而言,进程只能在父子进程之间进行通信 ,所以只需启动父进程就可以了,然后在父进程点击菜单“创建管道”即可创建子进程,这样匿名管道的父子进程就可以进行通信了
运行结果如下:
第三种:命名管道
用CreateNamedPipe(...)函数创建。
<1>首先我们先编写服务器端的程序:
(1)新建一个基于单文档对话框的工程,工程名为“NamedPipeSrv”
(2)添加如下菜单项并创建响应函数
(3)创建一个句柄变量用于保存管道句柄,HANDLE hPipe;并再其构造函数中初始化,在析构函数中释放该句柄。
CNamedPipeSrvView::CNamedPipeSrvView()
{
// TODO: add construction code here
hPipe=NULL;
}
CNamedPipeSrvView::~CNamedPipeSrvView()
{
if(hPipe)
CloseHandle(hPipe);
}
(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;
}
HANDLE hEvent;
//等待客户端连接请求的到来
hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
if(!hEvent)
{
MessageBox("创建事件对象失败!");
CloseHandle(hPipe);
hPipe=NULL;
return;
}
OVERLAPPED ovlap;
ZeroMemory(&ovlap,sizeof(OVERLAPPED));//将结构体内部的所有成员置为0
ovlap.hEvent=hEvent;
//The ConnectNamedPipe function enables a named pipe server process to wait for a client process to connect to an instance of a named pipe.
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
char buf[100];
DWORD dwRead;
if(!ReadFile(hPipe,buf,100,&dwRead,NULL))
{
MessageBox("读取数据失败!");
return;
}
MessageBox(buf);
}
void CNamedPipeSrvView::OnPipeWrite()
{
// TODO: Add your command handler code here
char buf[]="http://www.sunxin.org";
DWORD dwWrite;
if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写入数据失败!");
return;
}
}
<2>接下来编写客户端程序的编码,在现有项目下新增一个基于单文档对话框的工程,工程名为“NamedPipeClt”,和上一工程目录保持平级。
(1)添加如下菜单项并创建响应函数
(2)创建一个句柄变量用于保存管道句柄,HANDLE hPipe;并再其构造函数中初始化,在析构函数中释放该句柄
CNamedPipeCltView::CNamedPipeCltView()
{
// TODO: add construction code here
hPipe=NULL;
}
CNamedPipeCltView::~CNamedPipeCltView()
{
if(hPipe)
CloseHandle(hPipe);
}
(3) 为菜单项的“连接管道”、“读取数据”、“写入数据”做具体编码实现:
void CNamedPipeCltView::OnPipeConnect()
{
//The WaitNamedPipe function waits until either a time-out interval elapses or an instance of the specified named pipe is available for connection (that is, the pipe's server process has a pending ConnectNamedPipe operation on the pipe).
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;
}
}
void CNamedPipeCltView::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 CNamedPipeCltView::OnPipeWrite()
{
// TODO: Add your command handler code here
char buf[]="命名管道测试程序";
DWORD dwWrite;
if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写入数据失败!");
return;
}
}
运行结果如下:
第四种:油槽
(1)首先我们还是先编写服务器端代码,新建一个基于单文档对话框的工程,工程名为”MailslotSrv“
(2)创建如下菜单项,并添加命令响应函数。
(3)编写”接收数据“代码,对于油槽服务器端而已,它只能接收数据,所以代码页相对简单些
void CMailslotSrvView::OnMailslotRecv()
{
// TODO: Add your command handler code here
HANDLE hMailslot;//创建一个油槽句柄
hMailslot=CreateMailslot("\\\\.\\mailslot\\MyMailslot",0,MAILSLOT_WAIT_FOREVER,NULL);
if(INVALID_HANDLE_VALUE==hMailslot)
{
MessageBox("创建油槽失败!");
return ;
}
//若创建油槽成功,则读取句柄
char buf[100];
DWORD dwRead;
if(!ReadFile(hMailslot,buf,100,&dwRead,NULL))
{
MessageBox("读取数据失败!");
CloseHandle(hMailslot);//关闭句柄
return ;
}
MessageBox(buf);
CloseHandle(hMailslot);
}
<2>接下来编写客户端代码的实现,在现有项目下添加一个基于单文档对户口新的工程,工程名为”MailslotClt“
(1)添加如下菜单项,并设置其命令响应函数
(2)编写”发送数据“函数代码,同样,作为油槽客户端,它只能发送数据,所以代码量也相对较少。
void CMailslotCltView::OnPipeSend()
{
// TODO: Add your command handler code here
HANDLE hMailslot;
//打开油槽
hMailslot=CreateFile("\\\\.\\mailslot\\MyMailslot",GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(INVALID_HANDLE_VALUE==hMailslot)
{
MessageBox("打开油槽失败!");
return;
}
//若打开油槽成功,则发送数据
char buf[]="油槽客户端发送数据测试";
DWORD dwWrite;
if(!WriteFile(hMailslot,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写入数据失败!");
CloseHandle(hMailslot);
return;
}
CloseHandle(hMailslot);
}
进程间通讯的四种方式的优缺点:
剪贴板:只能在同一个机器上实现两个进程之间的通信,而不能跨网络
命名管道、油槽:不止能够在同一主机上的两个进程之间进行通讯,也可以实现跨网络的两个进程之间进行通讯。油槽可以实现一对多的广播通讯,而命名管道只能实现点对点之间的通信,即一对一通信。但是油槽有一个缺点,它发送的字节数比较少,管道则可以发送大量的数据。