您的位置:首页 > 其它

进程间通信 - 匿名管道实现

2013-04-18 00:03 344 查看
引子

前面的两篇博文分别介绍了通过剪贴板和通过邮槽来实现进程之间的通信,

其中剪贴板呢,是用来实现本地两个进程之间的通信,

而邮槽则既可以实现本地两个进程之间的通信,也可以实现本地进程和服务器进程的通信,

当然,上面这两种方式都是有局限性的,比如剪贴板就只能实现本地进程之间的通信,

而在邮槽中,虽然是既可以实现本地进程之间的通信,又能实现本地进程和远程服务器进程的通信,

但是使用邮槽的话,服务端只能接收数据,不能发送数据,而客户端只能发送数据而不能接收数据。

而本篇博文介绍的这个通过匿名管道来实现进程之间的通信的话,

局限性就显得更加严重了,

第一:匿名管道只能实现本地进程之间的通信,不能实现跨网络之间的进程间的通信。

第二:匿名管道只能实现父进程和子进程之间的通信,而不能实现任意两个本地进程之间的通信。

匿名管道概述

既然是匿名管道的话,自然,就是没有名字的管道了,还有一种管道呢,叫做命名管道,

命名管道的功能是很强大的,匿名管道在命名管道面前,功能那是简陋的不行的,

至于命名管道的话,会留到下一篇博文中介绍的,

匿名管道正因为提供的功能很单一,所以它所需要的系统的开销也就比命名管道小很多,

在本地机器上可以使用匿名管道来实现父进程和子进程之间的通信,

这里需要注意两点,第一就是在本地机器上,这是因为匿名管道不支持跨网络之间的两个进程之间的通信,

第二就是实现的是父进程和子进程之间的通信,而不是任意的两个进程。

然后得话还顺便介绍匿名管道的另外一种功能,其通过匿名管道可以实现子进程输出的重定向,

何为输出重定向呢?还请听下面详解:

比如我现在建立一个Win32的Console程序,然后在其中使用如下代码来输出一些信息:

#include<iostream>

usingnamespacestd;


intmain(intargc,char*argv)

{

cout<<"ZacharyXiaoZhen"<<endl<<endl;

cout<<"HappyNewYear"<<endl<<endl;


system("pause");

}


那么在默认下,编译运行上面的代码时,Windows会弹出一个黑框框,并且在这个黑框框中显示一些信息,





为什么一定要将输出的信息显示在这个黑框框中呢?有没有办法让其显示在我们自己定义的文本框中呢?

而后我们再看一幅截图:





上面画了很多红线的这个区域中的信息来自那里呢?为什么会在这个文本框中输出呢?

其实这就可以通过匿名管道来实现,

在卸载QQ游戏这幅截图中呢,其实运行了两个进程,

一个就是我们看到的这个输出了图形界面的进程,我们称之为卸载表象进程(父进程),

而另外一个用来执行真正意义上的卸载的进程我们称之为卸载实质进程(子进程)。

其实该卸载表象进程在其执行过程中创建了卸载实质进程来执行真正的卸载操作,

而后,卸载实质进程会输出上面用红色矩形标记的区域中的信息,

如果我们使用默认的输出的话,卸载实质进程会将上面红色区域标记中的信息输出到默认的黑框框中,

但是我们可以使用匿名管道来更改卸载实质进程的输出,

让其将输出数据输入到匿名管道中,而后卸载表象进程从匿名管道中读取到这些输出数据,

然后再将这些数据显示到卸载表象进程的文本框中就可以了。

而上面的这种用来更改卸载实质进程的输出的技术就称之为输出重定向。

当然与之相对的还有输入重定向的。

我们可以让一个进程的输入来自于匿名管道,而不是我们在黑框框中输入数据。

话说到这份上呢,也可以点出一点东东了,

上面的这个重定向不就是利用匿名管道实现的父进程和子进程之间的通信嘛。

匿名管道的使用

匿名管道主要用于本地父进程和子进程之间的通信,

在父进程中的话,首先是要创建一个匿名管道,

在创建匿名管道成功后,可以获取到对这个匿名管道的读写句柄,

然后父进程就可以向这个匿名管道中写入数据和读取数据了,

但是如果要实现的是父子进程通信的话,那么还必须在父进程中创建一个子进程,

同时,这个子进程必须能够继承和使用父进程的一些公开的句柄,

为什么呢?

因为在子进程中必须要使用父进程创建的匿名管道的读写句柄,

通过这个匿名管道才能实现父子进程的通信,所以必须继承父进程的公开句柄。

同时在创建子进程的时候,

必须将子进程的标准输入句柄设置为父进程中创建匿名管道时得到的读管道句柄,

将子进程的标准输出句柄设置为父进程中创建匿名管道时得到的写管道句柄。

然后在子进程就可以读写匿名管道了。

匿名管道的创建

BOOLWINAPICreatePipe(

__outPHANDLEhReadPipe,

__outPHANDLEhWritePipe,

__inLPSECURITY_ATTRIBUTESlpPipeAttributes,

__inDWORDnSize);


参数hReadPipe为输出参数,该句柄代表管道的读取句柄。

参数hWritePipe为输出参数,该句柄代表管道的写入句柄。

参数lpPipeAttributes为一个输入参数,指向一个SECURITY_ATTRIBUTES的结构体指针,

其检测返回的句柄是否能够被子进程继承,如果此参数为NULL,则表明句柄不能被继承,

在匿名管道中,由于匿名管道要在父子进程之间进行通信,

而子进程如果想要获得匿名管道的读写句柄,则其只能通过从父进程继承获得,

当一个子进程从其父进程处继承了匿名管道的读写句柄以后,

子进程和父进程之间就可以通过这个匿名管道的读写句柄进行通信了。

所以在这里必须构建一个SECURITY_ATTRIBUTES的结构体,

并且该结构体的第三个结构成员变量bInheritHandle参数必须设置为TRUE,

从而让子进程可以继承父进程所创建的匿名管道的读写句柄。

typedefstruct_SECURITY_ATTRIBUTES{


DWORDnLength;


LPVOIDlpSecurityDescriptor;


BOOLbInheritHandle;


}SECURITY_ATTRIBUTES,*LPSECURITY_ATTRIBUTES;


参数nSize用来指定缓冲区的大小,

如果此参数设置为0,则表明系统将使用默认的缓冲区大小。一般将该参数设置为0即可。

子进程的创建
BOOLCreateProcess(

LPCWSTRpszImageName,LPCWSTRpszCmdLine,

LPSECURITY_ATTRIBUTESpsaProcess,

LPSECURITY_ATTRIBUTESpsaThread,

BOOLfInheritHandles,DWORDfdwCreate,

LPVOIDpvEnvironment,LPWSTRpszCurDir,

LPSTARTUPINFOWpsiStartInfo,

LPPROCESS_INFORMATIONpProcInfo);


参数pszImageName是一个指向NULL终止的字符串,用来指定可执行程序的名称。

参数pszCmdLine用来指定传递给新进程的命令行字符串,一般做法是在pszImageName中传递可执行文件的名称,

在pszCmdLine中传递命令行参数。

参数psaProcess即代表当CreateProcess函数创建进程时,需要给进程对象设置一个安全性。

参数psaThread代表当CreateProcess函数创建新进程后,需要给该进程的主线程对象设置一个安全性。

参数fInheritHandles用来指定父进程随后创建的子进程是否能够继承父进程的对象句柄,

如果该参数设置为TRUE,则父进程的每一个可继承的打开句柄都将被子进程所继承,

继承的句柄与原始的句柄拥有同样的访问权。

在匿名管道的使用中,因为子进程需要使用父进程中创建的匿名管道的读写句柄,

所以应该将这个参数设置为TRUE,从而可以让子进程继承父进程创建的匿名管道的读写句柄。

参数fdwCreate用来指定控件优先级类和进程创建的附加标记。

如果只是为了启动子进程,则并不需要设置它创建的标记,可以将此参数设置为0,

对于这个参数的具体取值列表可以参考MSDN。

参数pvEnvironment代表指向环境块的指针,

如果该参数设置为NULL,则默认将使用父进程的环境。通常给该参数传递NULL。

参数pszCurDir用来指定子进程当前的路径,

这个字符串必须是一个完整的路径名,其包括驱动器的标识符,

如果此参数设置为NULL,那么新的子进程将与父进程拥有相同的驱动器和目录。

参数psiStartInfo指向一个StartUpInfo的结构体的指针,用来指定新进程的主窗口如何显示。

typedefstruct_STARTUPINFOA{


DWORDcb;


LPSTRlpReserved;


LPSTRlpDesktop;


LPSTRlpTitle;


DWORDdwX;


DWORDdwY;


DWORDdwXSize;


DWORDdwYSize;


DWORDdwXCountChars;


DWORDdwYCountChars;


DWORDdwFillAttribute;


DWORDdwFlags;


WORDwShowWindow;


WORDcbReserved2;


LPBYTElpReserved2;


HANDLEhStdInput;


HANDLEhStdOutput;


HANDLEhStdError;


}STARTUPINFOA,*LPSTARTUPINFOA;


对于dwFlags参数来说,如果其设置为STARTF_USESTDHANDLES,

则将会使用该STARTUPINFO结构体中的hStdInput,hStdOutput,hStdError成员,

来设置新创建的进程的标准输入,标准输出,标准错误句柄。

参数pProcInfo为一个输出参数,

指向一个PROCESS_INFORMATION结构体的指针,用来接收关于新进程的标识信息。

typedefstruct_PROCESS_INFORMATION

{

HANDLEhProcess;

HANDLEhThread;

DWORDdwProcessId;

DWORDdwThreadId;


}PROCESS_INFORMATION;


其中hProcess和hThread分别用来标识新创建的进程句柄和新创建的进程的主线程句柄。

dwProcessId和dwThreadId分别是全局进程标识符和全局线程标识符。

前者可以用来标识一个进程,后者用来标识一个线程。

示例:匿名管道实现父子进程间通信

父进程实现:(简单MFC程序)

项目结构:





消息以及成员函数和成员变量的声明:

public:

//创建匿名管道

afx_msgvoidOnBnClickedBtnCreatePipe();

//写匿名管道

afx_msgvoidOnBnClickedBtnWritePipe();

//读匿名管道

afx_msgvoidOnBnClickedBtnReadPipe();


//定义父进程读匿名管道的成员函数

voidParentReadPipe(void);

//定义父进程写匿名管道的成员函数

voidParentWritePipe(void);

//创建SECURITY_ATTRIBUTES结构的成员函数

voidCreateSecurityAttributes(PSECURITY_ATTRIBUTESpSa);

//创建STARTUPINFO结构的成员函数

voidCreateStartUpInfo(LPSTARTUPINFOlpStartUpInfo);

//创建匿名管道的成员函数

voidCreateNoNamedPipe(void);


//分别代表要从匿名管道中读的数据和要写到匿名管道中的数据

CStringm_CStrReadPipe;

CStringm_CStrWritePipe;


//保存创建匿名管道后所得到的对匿名管道的读写句柄

HANDLEhPipeRead;

HANDLEhPipeWrite;


//保证匿名管道只创建一次

BOOLm_PipeIsCreated;


消息映射表定义:

constintdataLength=100;


CNoNamedPipeParentDlg::CNoNamedPipeParentDlg(CWnd*pParent/*=NULL*/)

:CDialogEx(CNoNamedPipeParentDlg::IDD,pParent)

,m_CStrReadPipe(_T(""))

{

m_hIcon=AfxGetApp()->LoadIcon(IDR_MAINFRAME);


m_PipeIsCreated=FALSE;

}


voidCNoNamedPipeParentDlg::DoDataExchange(CDataExchange*pDX)

{

CDialogEx::DoDataExchange(pDX);

DDX_Text(pDX,IDC_EDIT_WRITE_PIPE,m_CStrWritePipe);

DDX_Text(pDX,IDC_EDIT_READ_PIPE,m_CStrReadPipe);

}


BEGIN_MESSAGE_MAP(CNoNamedPipeParentDlg,CDialogEx)

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_BN_CLICKED(IDC_BTN_CREATE_PIPE,

&CNoNamedPipeParentDlg::OnBnClickedBtnCreatePipe)

ON_BN_CLICKED(IDC_BTN_WRITE_PIPE,

&CNoNamedPipeParentDlg::OnBnClickedBtnWritePipe)

ON_BN_CLICKED(IDC_BTN_READ_PIPE,

&CNoNamedPipeParentDlg::OnBnClickedBtnReadPipe)

END_MESSAGE_MAP()


消息处理函数:

//创建匿名管道按钮的消息处理函数

voidCNoNamedPipeParentDlg::OnBnClickedBtnCreatePipe()

{

if(m_PipeIsCreated==FALSE)

{

this->CreateNoNamedPipe();

}

}



//写入数据到匿名管道中按钮的消息处理函数

voidCNoNamedPipeParentDlg::OnBnClickedBtnWritePipe()

{

this->ParentWritePipe();

}



//从匿名管道中读取数据按钮的消息处理函数

voidCNoNamedPipeParentDlg::OnBnClickedBtnReadPipe()

{

this->ParentReadPipe();

}



//接收数据

voidCNoNamedPipeParentDlg::ParentReadPipe(void)

{

DWORDdwRead;

char*pReadBuf;

CStringcStrRecvData;


pReadBuf=newchar[dataLength];

memset(pReadBuf,0,dataLength);


if(!ReadFile(hPipeRead,pReadBuf,dataLength,&dwRead,NULL))

{

MessageBox(TEXT("从匿名管道接收数据失败..."),

TEXT("提示"),MB_ICONERROR);

return;

}


cStrRecvData="从匿名管道接收数据成功:";

cStrRecvData+=pReadBuf;


this->m_CStrReadPipe.Empty();


this->m_CStrReadPipe=pReadBuf;

UpdateData(FALSE);


MessageBox(cStrRecvData,TEXT("提示"),MB_ICONINFORMATION);

}



//发送数据

voidCNoNamedPipeParentDlg::ParentWritePipe(void)

{

UpdateData();


if(!this->m_CStrWritePipe.IsEmpty())

{

char*pSendData;

DWORDdwWrite;

CStringcStrSendData;


//在这里需要将Unicode字符集转换为ASCII字符集

pSendData=newchar[this->m_CStrWritePipe.GetLength()+1];

memset(pSendData,0,this->m_CStrWritePipe.GetLength()+1);

for(inti=0;i<this->m_CStrWritePipe.GetLength();i++)

{

pSendData[i]=(char)this->m_CStrWritePipe.GetAt(i);

}


if(!WriteFile(hPipeWrite,pSendData,

this->m_CStrWritePipe.GetLength()+1,&dwWrite,NULL))

{

MessageBox(TEXT("给匿名管道发送数据失败..."),

TEXT("提示"),MB_ICONERROR);

return;

}


cStrSendData="给匿名管道发送数据成功:";

cStrSendData+=this->m_CStrWritePipe;


this->m_CStrWritePipe.Empty();

UpdateData(FALSE);


MessageBox(cStrSendData,TEXT("提示"),MB_ICONINFORMATION);

}

else

{

MessageBox(TEXT("请先输入要发送给匿名管道的数据..."),

TEXT("提示"),MB_ICONERROR);

}

}



//创建SECURITY_ATTRIBUTES结构

voidCNoNamedPipeParentDlg::CreateSecurityAttributes(PSECURITY_ATTRIBUTESpSa)

{

//这里必须将bInheritHandle设置为TRUE,

//从而使得子进程可以继承父进程创建的匿名管道的句柄

pSa->bInheritHandle=TRUE;

pSa->lpSecurityDescriptor=NULL;

pSa->nLength=sizeof(SECURITY_ATTRIBUTES);

}



//用来初始化新进程的STARTUPINFO成员

voidCNoNamedPipeParentDlg::CreateStartUpInfo(LPSTARTUPINFOlpStartUpInfo)

{

memset(lpStartUpInfo,0,sizeof(STARTUPINFO));


lpStartUpInfo->cb=sizeof(STARTUPINFO);

lpStartUpInfo->dwFlags=STARTF_USESTDHANDLES;


//子进程的标准输入句柄为父进程管道的读数据句柄

lpStartUpInfo->hStdInput=hPipeRead;


//子进程的标准输出句柄为父进程管道的写数据句柄

lpStartUpInfo->hStdOutput=hPipeWrite;


//子进程的标准错误处理句柄和父进程的标准错误处理句柄一致

lpStartUpInfo->hStdError=GetStdHandle(STD_ERROR_HANDLE);

}



//创建匿名管道

voidCNoNamedPipeParentDlg::CreateNoNamedPipe(void)

{

SECURITY_ATTRIBUTESsa;

PROCESS_INFORMATIONprocessInfo;

STARTUPINFOstartUpInfo;


CreateSecurityAttributes(&sa);

if(!CreatePipe(&hPipeRead,&hPipeWrite,&sa,0))

{

MessageBox(TEXT("创建匿名管道失败..."),

TEXT("提示"),MB_ICONERROR);

return;

}


CreateStartUpInfo(&startUpInfo);

if(!CreateProcess(TEXT("NoNamedPipeChild.exe"),

NULL,NULL,NULL,TRUE,

CREATE_NEW_CONSOLE,NULL,NULL,

&startUpInfo,&processInfo))

{

CloseHandle(hPipeRead);

CloseHandle(hPipeWrite);


hPipeWrite=NULL;

hPipeRead=NULL;


MessageBox(TEXT("创建子进程失败..."),

TEXT("提示"),MB_ICONERROR);

return;

}

else

{

m_PipeIsCreated=TRUE;


//对于processInfo.hProcess和processInfo.hThread

//这两个句柄不需要使用,所以释放资源

CloseHandle(processInfo.hProcess);

CloseHandle(processInfo.hThread);

}

}


子进程实现:(简单MFC程序)

项目结构:





消息以及成员函数和成员变量的声明:

//实现

protected:

HICONm_hIcon;


//生成的消息映射函数

virtualBOOLOnInitDialog();

afx_msgvoidOnPaint();

afx_msgHCURSOROnQueryDragIcon();

DECLARE_MESSAGE_MAP()

public:

afx_msgvoidOnBnClickedBtnWritePipe();

afx_msgvoidOnBnClickedBtnReadPipe();


//保存从父进程得到针对于匿名管道的读写句柄

HANDLEhPipeRead;

HANDLEhPipeWrite;


//分别代表要从匿名管道中读的数据和要写到匿名管道中的数据

CStringm_CStrWritePipe;

CStringm_CStrReadPipe;


//子进程读取匿名管道

voidChildReadPipe(void);

//子进程写匿名管道

voidChildWritePipe(void);

//子进程获取从父进程处继承得到的关于匿名管道的读写句柄

voidGetReadWriteHandleFromParent(void);


//只需要获取一次匿名管道的读写句柄即可

BOOLm_IsGettedParentHandle;


消息映射表定义:

constintdataLength=100;


CNoNamedPipeChildDlg::CNoNamedPipeChildDlg(CWnd*pParent/*=NULL*/)

:CDialogEx(CNoNamedPipeChildDlg::IDD,pParent)

,m_CStrWritePipe(_T(""))

,m_CStrReadPipe(_T(""))

{

m_hIcon=AfxGetApp()->LoadIcon(IDR_MAINFRAME);


this->m_IsGettedParentHandle=FALSE;

}


voidCNoNamedPipeChildDlg::DoDataExchange(CDataExchange*pDX)

{

CDialogEx::DoDataExchange(pDX);

DDX_Text(pDX,IDC_EDIT_WRITE_PIPE,m_CStrWritePipe);

DDX_Text(pDX,IDC_EDIT_READ_PIPE,m_CStrReadPipe);

}


BEGIN_MESSAGE_MAP(CNoNamedPipeChildDlg,CDialogEx)

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_BN_CLICKED(ID_BTN_WRITE_PIPE,

&CNoNamedPipeChildDlg::OnBnClickedBtnWritePipe)

ON_BN_CLICKED(ID_BTN_READ_PIPE,

&CNoNamedPipeChildDlg::OnBnClickedBtnReadPipe)

END_MESSAGE_MAP()


消息处理函数:

//往匿名管道中写入数据按钮的消息处理函数

voidCNoNamedPipeChildDlg::OnBnClickedBtnWritePipe()

{

//如果子进程还没有获得对匿名管道的读写句柄的话需要先获取句柄

this->GetReadWriteHandleFromParent();


ChildWritePipe();

}



//从匿名管道中读取数据按钮的消息处理函数

voidCNoNamedPipeChildDlg::OnBnClickedBtnReadPipe()

{

//如果子进程还没有获得对匿名管道的读写句柄的话需要先获取句柄

this->GetReadWriteHandleFromParent();


ChildReadPipe();

}


//从匿名管道读取数据成员函数

voidCNoNamedPipeChildDlg::ChildReadPipe(void)

{

DWORDdwRead;

char*pReadBuf;

CStringcStrRecvData;


pReadBuf=newchar[dataLength];

memset(pReadBuf,0,dataLength);


//读取数据

if(!ReadFile(hPipeRead,pReadBuf,dataLength,&dwRead,NULL))

{

MessageBox(TEXT("从匿名管道接收数据失败..."),

TEXT("提示"),MB_ICONERROR);

return;

}


cStrRecvData="从匿名管道接收数据成功:";

cStrRecvData+=pReadBuf;


this->m_CStrReadPipe.Empty();

this->m_CStrReadPipe=pReadBuf;

UpdateData(FALSE);


MessageBox(cStrRecvData,TEXT("提示"),MB_ICONINFORMATION);

}


//往匿名管道中写入数据

voidCNoNamedPipeChildDlg::ChildWritePipe(void)

{

UpdateData();


if(!this->m_CStrWritePipe.IsEmpty())

{

char*pSendData;

DWORDdwWrite;

CStringcStrSendData;


//在这里需要将Unicode字符集转换为ASCII字符集

pSendData=newchar[this->m_CStrWritePipe.GetLength()+1];

memset(pSendData,0,this->m_CStrWritePipe.GetLength()+1);

for(inti=0;i<this->m_CStrWritePipe.GetLength();i++)

{

pSendData[i]=(char)this->m_CStrWritePipe.GetAt(i);

}


//写入数据

if(!WriteFile(hPipeWrite,pSendData,

this->m_CStrWritePipe.GetLength(),&dwWrite,NULL))

{

MessageBox(TEXT("给匿名管道发送数据失败..."),

TEXT("提示"),MB_ICONERROR);

return;

}


cStrSendData="给匿名管道发送数据成功:";

cStrSendData+=this->m_CStrWritePipe;


this->m_CStrWritePipe.Empty();

UpdateData(FALSE);


MessageBox(cStrSendData,TEXT("提示"),MB_ICONINFORMATION);

}

else

{

MessageBox(TEXT("请先输入要发送给匿名管道的数据..."),

TEXT("提示"),MB_ICONERROR);

}

}


//需要获取继承自父进程的匿名管道读写句柄

voidCNoNamedPipeChildDlg::GetReadWriteHandleFromParent(void)

{

if(this->m_IsGettedParentHandle==FALSE)

{

hPipeRead=GetStdHandle(STD_INPUT_HANDLE);

hPipeWrite=GetStdHandle(STD_OUTPUT_HANDLE);


this->m_IsGettedParentHandle=TRUE;

}

}


效果展示:

首先需要将子进程的可执行文件拷贝到父进程所在目录下,否则创建进程时会找不到子进程的可执行文件。





启动父进程可执行文件,并单击创建匿名管道按钮,此时会弹出子进程窗口(新建了进程):





再在父进程的左边文本框中输入数据,单击写入数据按钮:





再在子进程窗口中单击读取数据按钮:





再在子进程窗口左边的文本框中输入数据,单击写入数据按钮:





再在父进程窗口中单击读取数据按钮:





结束语

从上面的效果展示中就可以看出我们确实是实现了父子进程之间通过匿名管道来进行通信,

最后再来总结一下对于匿名管道的使用,

匿名管道一般用于实现本地父子进程之间的通信,其不能实现跨网络进程之间的通信,

同时其也一般只用于实现父进程和子进程之间的通信。

像匿名管道的话,其和邮槽不同,

其可以实现父进程即可以向子进程发送数据,同时父进程又可以从子进程接收到数据。

而且子进程可以接收来自父进程的数据,并且也可以给父进程发送数据。转载自Zachary.XiaoZhen
-梦想的天空
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: