进程通信4种方式
1.前面的剪贴板2.匿名管道3.命名管道4.油槽
匿名管道
父进程
void CParentView::OnPipeCreate()
{
// TODO: Add your command handler code here
SECURITY_ATTRIBUTES sa;
sa.bInheritHandle=TRUE;//指定匿名管道 句柄是否可以被继承
sa.lpSecurityDescriptor=NULL;//创建管道的安全性
sa.nLength=sizeof(SECURITY_ATTRIBUTES);//结构体大小
if (!CreatePipe(&hRead,&hWrite,&sa,0))//创建匿名管道 第1,2参数返回读和写的句柄,第三个指向一结构体见上面,第四个表明匿名管道使用默认的缓冲区大小
{
MessageBox("管道创建失败");
return;
}
STARTUPINFO sui;
PROCESS_INFORMATION pi;
memset(&sui,0,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))/*创建个启动子进程 第五个参数注意设为TRUE表示子进程可以继承父进程的所有句柄 第八个参数指定子进程的路径而且必须指定盘符 如果为NULL表明和父进程有同一个盘符和目录*/
{
CloseHandle(hRead);
CloseHandle(hWrite);
hRead=NULL;
hWrite=NULL;
MessageBox("子进程创建失败");
return;
}
else
{
CloseHandle(pi.hProcess); //记得关掉子进程的线程句柄
CloseHandle(pi.hThread); //记得关掉子进程的句柄 因为CreateProcess创建 + 打开了他们一次 所以因为他们是内核对象有使用计数 所以 应该如果不关此时计数是2
}
}
//读取 和 写入 与子进程
void CParentView::OnPipeRead()
{
// TODO: Add your command handler code here
char buf[100];
DWORD ByteRead;
if(!ReadFile(hRead,&buf,100,&ByteRead,NULL))
{
MessageBox("读取数据失败");
return;
}
MessageBox(buf);
}
void CParentView::OnPipeWrite()
{
// TODO: Add your command handler code here
char buf[]="父进程管道写入测试";
DWORD ByteWrite;
if (!WriteFile(hWrite,(LPVOID)buf,strlen(buf)+1,&ByteWrite,NULL))
{
MessageBox("写入数据失败");
return;
}
}
子进程
void CChildView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
//刚刚在父进程中 在一个sui的对象中将 子进程的 输入输出对象给了他们的 可以通过GetStdHandle取出来 可以通过这两个对象进行读和写 我这里就省略了.
hRead=GetStdHandle(STD_INPUT_HANDLE);
hWrite=GetStdHandle(STD_OUTPUT_HANDLE);
}
命名管道
命名管道因为 可以跨网络的 所以分了服务端和客户端
服务端
//创建命名管道
void CNamedPipeSrvView::OnPipeCreate()
{
// TODO: Add your command handler code here
/*
这是创建命名管道的函数 第一个参数如果不是和本地的进程而是跨网络的 那.(点)后面应该是服务器的名称 pipe固定写法 MyPipe管道名称可自定义
第二个参数 指明双向访问方式和重叠操作
第三个参数 管道类型 0表明字节类型管道 读取方式也是字节类型读取 并且是等待方式 是一直等的也就是阻塞模式的
第四个参数 表明可以创建的实例最大数目数 我们设为了1
第五六个参数 表明 读取和写入的缓冲区大小吧 我们设为了1024字节
第七个参数 指定默认超时时间 我们指定为了0 同一管道不同实例 也必须这个值
第八个参数 安全性NULL 默认的安全性
*/
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;
}
//因为是重叠操作滴 所以ConnectNamedPipe等待连接操作之前 必须创建一个人工事件对象
HANDLE hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
if (!hEvent)
{
MessageBox("事件对象创建失败!");
return;
}
OVERLAPPED ovlp;
//用此函数现将结构体其余部分设为0 避免函数操作
memset(&ovlp,0,sizeof(OVERLAPPED));
//指定事件对象
ovlp.hEvent=hEvent;
//等待客户端的请求 连接 ,这个操作如果成功执行完后 刚刚的人工事件对象 会成为有信号的状态.
if(!ConnectNamedPipe(hPipe,&ovlp))
{
if (ERROR_IO_PENDING!=GetLastError())
{
MessageBox("管道等待连接失败!");
CloseHandle(hPipe);
hPipe=NULL;
CloseHandle(hEvent);
return;
}
}
//等待获取人工事件对象.
if(WAIT_FAILED==WaitForSingleObject(hEvent,INFINITE))
{
MessageBox("等待对象失败!");
CloseHandle(hEvent);
CloseHandle(hPipe);
hPipe=NULL;
return;
}
CloseHandle(hEvent);
}
//读和写 部分的编写
//读
void CNamedPipeSrvView::OnPipeRecv()
{
// TODO: Add your command handler code here
char recvBuf[100];
DWORD recvCz;
if(!ReadFile(hPipe,recvBuf,100,&recvCz,NULL)) //想不通一点 孙鑫说 重叠操作 读取数据这里操作没完成也可以立即返回 ,完成后系统会通知,没完成这里返回的什么值?
{
MessageBox("读取数据失败!");
return;
}
MessageBox(recvBuf);
}
//写
void CNamedPipeSrvView::OnPipeSend()
{
// TODO: Add your command handler code here
DWORD sendCz;
if (!WriteFile(hPipe,"服务端命名管道写入测试",strlen("服务端命名管道写入测试")+1,&sendCz,NULL))
{
MessageBox("写入数据失败!");
return;
}
}
客户端
//等待可用管道 与打开
void CNamedPipeCltView::OnPipeConnet()
{
// TODO: Add your command handler code here
//等待可用的管道 第一个参数上面说了不解释 第二个参数让他一直等待
if(!WaitNamedPipe("\\\\.\\pipe\\Mypipe",NMPWAIT_WAIT_FOREVER))
{
MessageBox("等待可用管道失败!");
return;
}
/*
打开可用管道 第一个参数 不解释
第二个参数 表明你打开了是读还是写
第三个参数 表明共享 不共享则为0
第四个参数 默认安全性
第五个参数 表明打开现已存在的
第六个参数 属性 为默认不同
第七个参数 模板为空
*/
hPipe=CreateFile("\\\\.\\pipe\\Mypipe",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (INVALID_HANDLE_VALUE==hPipe)
{
MessageBox("打开管道失败!");
CloseHandle(hPipe);
hPipe=NULL;
return;
}
}
//得到命名管道的句柄后 就可以利用 WriteFile 和ReadFile 和服务端进程进行读和写了 这里省略不写
油槽
油槽也是基于网络的 所以油槽也分服务端和客户端 并且是广播式的 可以一对多 不过他对传输的数据大小有限制 不能大于420字节
服务端只能接受数据 客户端发送, 当然如果想要在一个程序中同时有接受和发送的功能 ,你可以把客户端的发送代码集成到服务端中.
服务端
void CMailSlotView::OnMailslotRecv()
{
// TODO: Add your command handler code here
/*
创建油槽 第一个参数写法见命名管道的那个就懂了
第二个参数 指定写入到油槽消息的大小 我们让其可以发送任意大小 所以设为0
第三个参数 等待超时值 为0没有消息则立刻返回 大于0的话呢在超时之前可以等待知道消息来到或超时结束 我们让其一直等待知道有消息可用
第四个参数 默认安全性NULL
hMailslot=CreateMailslot("\\\\.\\mailslot\\Mymailslot",0,MAILSLOT_WAIT_FOREVER,NULL);
if (INVALID_HANDLE_VALUE==hMailslot)
{
printf("油槽创建失败");
hMailslot=NULL;
return;
}
char recvBuf[100];
DWORD recvCz;
//读取数据
if(!ReadFile(hMailslot,recvBuf,100,&recvCz,NULL))
{
MessageBox("读取数据失败!");
return;
}
MessageBox(recvBuf);
CloseHandle(hMailslot);
}
客户端
void CMailSlotCltView::OnMailslotSend()
{
// TODO: Add your command handler code here
//打开油槽 第三个参数指定为共享可读 说是服务器那边要读 想不通为何命名管道那不用 就当硬性规定吧
hMailSlot=CreateFile("\\\\.\\mailslot\\Mymailslot",GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (INVALID_HANDLE_VALUE==hMailSlot)
{
hMailSlot=NULL;
MessageBox("打开油槽失败!");
return;
}
DWORD sendCz;
//写 发送数据
if (!WriteFile(hMailSlot,"客户端命名管道写入测试",strlen("客户端命名管道写入测试")+1,&sendCz,NULL))
{
MessageBox("写入数据失败!");
return;
}
CloseHandle(hMailSlot);
}
总结
1.匿名管道 :只能在本地进程间进行通信 并且只能是父子进程间通信 子进程必须由父进程创建才能通信 完成句柄的继承
2.命名管道 :基于网络的分了服务器和客户端来进行通信
3.油槽 :基于网络的广播式的 对传输数据大小有限制 420字节左右
4.剪贴板 :不解释