进程间通信——命名管道和邮槽
前面介绍了进程间通信的两种方法:剪贴板和匿名管道。这两种进程间通信的方法只能在本地主机的进程之间通信。而匿名管道还限制通信的两进程之间必须有父子关系。在开发网络间不同进程之间相互通信的应用程序时,我们可以用命名管道和邮槽。这两种方法不仅支持本地主机通信也支持网间进程通信。下面详细介绍这两种方法:
一、命名管道
将命名管道作为网络通信的方案时,他实际上建立了一个客户机/服务器通信体系,并在其中可靠的传输数据。命名管道式围绕Windows文件系统设计的一种机制,采用“命名管道文件系统借口”,因此客户机和服务器可利用标准的Win32文件系统函数来进行数据的收发。在利用命名管道进行通信时,服务器是唯一一个有权建立命名管道的进程,可以接收客户机的连接请求,客户机只能同一个现有的命名管道客户机建立连接。命名管道提供了两种基本的通信模式:字节模式和消息模式。在实际编程过程中可以根据实际需要选择不同的通信模式。利用命名管道进行通信可以按照下面的步骤进行:
(一)服务器端:
(1)创建命名管道
在利用命名管道进行通信之前,肯定要先创建命名管道。创建命名管道需调用的函数为CreateNamedPipe(),该函数的创建一个命名管道的实例并返回该实例的句柄。该函数的第一个参数用指定的格式保存创建的管道的名字。该函数的饿第二个参数指定管道的进入模式、重叠模式、读写模式、安全属性等信息,在下面的示例中我用的是双向的进入模式(PIPE_ACCESS_DUPLEX),客户机和服务器都可以从管道读写数据,重叠方式为可重叠的方式(FILE_FLAG_OVERLAPPED),指定重叠模式后,需要花费时间的线程会立即返回执行其他操作,耗费时间的线程会在后台完成。该函数的第四个参数指定该管道可创建的最大实例个数。指定该参数的原因是,一个命名管道的示例只能和一个客户端通信,如果有多个客户端要通过该命名管道和服务器通信,需要创建多个该命名管道的实例。创建命名管道的示例代码如下:
/***************************************************
创建命名管道
*******************************************************/
hPipe=CreateNamedPipe("\\\\.\\pipe\\MyPipe",PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,
0,1,1024,1024,0,NULL);
if (INVALID_HANDLE_VALUE==hPipe)
{
MessageBox("创建命名管道失败!");
hPipe=NULL;
//CloseHandle(hPipe);
return;
}
(2)等待客户端连接请求的到来
命名管道创建完成之后,服务器就可以等待客户端的连接请求。等待客户端的连接请求调用的函数为ConnectNamedPipe()该函数的的第二个参数是一个指向OVERLAPPED结构体的指针,该结构体包含用于异步执行I/O操作的信息。该结构体中我们感兴趣的参数是一个指向事件对象的句柄,我们可以利用该事件对象来执行异步I/O操作。所以我们要先调用CreateEvent()创建一个人工重置的事件对象。当等待客户端连接请求之后,我们就可以调用WaitForSingleObject()函数将该事件对象置为无信号状态,以供下一次连接请求使用。实现代码如下:
/*********************************************************
创建人工重置的匿名事件对象
*********************************************************/
HANDLE hEvent;
hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
if (!hEvent)
{
MessageBox(" 创建人工重置的匿名事件对象失败!");
CloseHandle(hEvent);
hPipe=NULL;
return;
}
/*********************************************************
等待客户端连接请求的到来
*********************************************************/
OVERLAPPED olp;
ZeroMemory(&olp,sizeof(OVERLAPPED));
olp.hEvent=hEvent;
if(!ConnectNamedPipe(hPipe,&olp))
{
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);
(3)往管道中执行读写操作
由于命名管道式围绕文件系统设计的,所以我们可以利用标准的Win32文件操作函数执行读写操作。示例代码如下:
void CNamedPipeSrvView::OnPipRead()
{
// TODO: Add your command handler code here
char buf[200];
DWORD dwRead;
if(!ReadFile(hPipe,buf,200,&dwRead,NULL))
{
MessageBox("读取匿名管道失败!");
return;
}
MessageBox(buf);
}
void CNamedPipeSrvView::OnPipWrite()
{
// TODO: Add your command handler code here
char buf[]="匿名管道测试程序!";
DWORD dwWrite;
if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写匿名管道失败!");
return;
}
}
(二)客户端
命名管道的实现比较简单了,客户端首先判断是否有可利用的命名管道实例,如果有,然后打开管道就可以进行读写操作了。
(1)连接管道实例并打开管道
WaitNamedPipe()函数可用来连接一个命名管实例,连接成功后,就可以调用CreateFile()函数打开管道,该函数可以指定管道进入的模式、文件属性等。示例代码如下:
**********************************************************
判断是否有可利用的命名管道实例
************************************************************/
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;
}
(2)往管道进行读写操作
void CNamedPipeCltView::OnPipRead()
{
// TODO: Add your command handler code here
char buf[200];
DWORD dwRead;
if(!ReadFile(hPipe,buf,200,&dwRead,NULL))
{
MessageBox("读取匿名管道失败!");
return;
}
MessageBox(buf);
}
void CNamedPipeCltView::OnPipWrite()
{
// TODO: Add your command handler code here
char buf[]="乘风736博客园 http://www.cnblogs.com/chengfeng736/";
DWORD dwWrite;
if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写匿名管道失败!");
return;
}
}
运行结果如下:
这样,一个通过命名管道进行通信的服务器和客户端程序就设计好了。两个进程就可以实现通信了。
二 、邮槽
邮槽是基于广播模式的单向通信方式,服务器只能从邮槽读取数据,客户端只能往邮槽写入数据,而且利用邮槽通信的信息量不能太大。下面介绍利用邮槽进行进程通信的过程:
(一)服务器
(1)创建油槽
创建油槽可调用CreateMailslot()函数实现,该函数的第一个参数按照指定格式指定油槽的名字,第三个参数指定读操作等待的时间。下面的示例程序我设定读操作一直等待(MAILSLOT_WAIT_FOREVER),只到接收到数据为止。示例代码如下:
HANDLE hMailslot;
hMailslot=CreateMailslot("\\\\.\\mailslot\\MyMailslot",0,MAILSLOT_WAIT_FOREVER,NULL);
if (INVALID_HANDLE_VALUE==hMailslot)
{
MessageBox("创建邮槽失败!");
CloseHandle(hMailslot);
return;
}
(2)读取数据
DWORD dwRead;
char buf[200];
if(!ReadFile(hMailslot,buf,200,&dwRead,NULL))
{
MessageBox("读取数据失败!");
CloseHandle(hMailslot);
return;
}
MessageBox(buf);
CloseHandle(hMailslot);
(二)客户端
(1)打开油槽
CreateFile()函数不仅可以打开文件、管道还可以打开油槽。示例代码如下:
HANDLE hMailslot;
hMailslot=CreateFile("\\\\.\\mailslot\\MyMailslot",GENERIC_WRITE,FILE_SHARE_READ,
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (INVALID_HANDLE_VALUE==hMailslot)
{
MessageBox("打开邮槽失败!");
CloseHandle(hMailslot);
return;
}
(2)写入数据
char buf[]="使用邮槽进行进程间通信\r\n乘风736博客园 http://www.cnblogs.com/chengfeng736/";
DWORD dwWrite;
if(!WriteFile(hMailslot,buf,strlen(buf)+1,&dwWrite,0))
{
MessageBox("写入数据失败!");
CloseHandle(hMailslot);
return;
}
CloseHandle(hMailslot);
运行结果如下:
使用油槽通信的实现是很简单的。油槽只能单向通信,如果要实现双向通信,可以在客户端和服务器分别实现读写操作就可以了。