最新消息:热烈庆祝IT小记上线!

详细讲解进程间通讯的四种方式

进程间通讯的四种方式:剪贴板、匿名管道、命名管道和邮槽

第一种:剪贴板

(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);
}



进程间通讯的四种方式的优缺点:

剪贴板:只能在同一个机器上实现两个进程之间的通信,而不能跨网络
命名管道、油槽:不止能够在同一主机上的两个进程之间进行通讯,也可以实现跨网络的两个进程之间进行通讯。油槽可以实现一对多的广播通讯,而命名管道只能实现点对点之间的通信,即一对一通信。但是油槽有一个缺点,它发送的字节数比较少,管道则可以发送大量的数据。


12月就这样end 下一篇

猜您喜欢

备案号:苏ICP备12016861号-4