创建匿名管道进行本地信息的交互,因为这个通信的过程一直比较模糊,不明白数据的流动方向,所以自己通过了解匿名管道,编写代码来加强自己对匿名管道的认识。因为是第一次书写文章,可能会有错误的地方,请多多关照。
管道的知识就不多做解释了,这里主要是记录我通过匿名管道进行父子进程之间信息交互的问题和解决方法。
对于首先创建的两个匿名管道,通过读写句柄,完成父进程与子进程之间的信息交互;该程序是将子程序控制的控制台进出输出-->管道1-->父进程通过readfile函数读取管道的数据,由父进程掌握管道2的写句柄-->管道2-->子进程接受命令;
编程方面的问题点:1.对于writefile和readfile函数的应用,
BOOL WriteFile(
HANDLE hFile, // 文件句柄
LPCVOID lpBuffer, // 数据缓存区指针
DWORD nNumberOfBytesToWrite, // 你要写的字节数
LPDWORD lpNumberOfBytesWritten, // 用于保存实际写入字节数的存储区域的指针
LPOVERLAPPED lpOverlapped // OVERLAPPED结构体指针
);
BOOL ReadFile(
HANDLE hFile, //指向要读取文件的句柄
LPVOID lpBuffer, //用于保存读入数据的一个缓冲区
DWORD nNumberOfBytesToRead, //要从文件读入的字符数
LPDWORD lpNumberOfBytesRead, //指向实际读取字节数的指针
LPOVERLAPPED lpOverlapped //如文件打开时指定了FILE_FLAG_OVERLAPPED,那么必须,用这个参数引用一个特殊的结构。该结构定义了一次异步读取操作。否则,应将这个参数设为NULL
);
从上面的函数说明中可以发现,这两个函数是非常相近的,但是要注意第三个参数的表示,对于writefile来说 是我通过创建匿名管道得到的写句柄 往管道里写入我想传输的信息,对于第三个参数只需要表达出我第二个参数char[] 到底有多少数据,使用strlen就行;而对于readfile函数,就是父进程要从管道中读取数据,对于数据量是未知,你只能对这个数组使用sizeof,也就是整个数组的空间都包含上,避免内存分配出错。
2.对于字符串的转换
这是父进程将需要的命令写入管道中,使其子进程从管道的另一端得到命令,也就是在cmd.exe程序下执行;那么平常要在控制台中从键盘输入命令后得到执行的结果,命令是以换行符为输入命令结束标志的,那么在父进程传个子进程的命令数组中也应该在最后有一个’\n’,表示人为动作结束指令的标志。
代码如下:
#include <stdio.h>
#include <Windows.h>
#include <cstdio>
#include <map>
#include <string>
#include <iostream>
#include <vector>
#include <list>
using namespace std;
int main()
{
//初始化
HANDLE hReadPipe1,hWritePipe1,
hReadPipe2,hWritePipe2; //创建两对读写句柄
string recvBuff; //父进程接收的数据
char sendBuff[5000] = {0}; //父进程发送的指令
//string sendBuff;
char Buff[5000]={0}; //临时数据储存缓存区
DWORD lBytesRead=0,lBytesWrite=0; //存放实际读写字节的变量,能观察到从管道中读取的了多少字节 方便断点调试查看
int iCounter=0; //要完成的几次交互
string strcmd; //指令字符串
map<int ,string> cmd; //简单命令容器 做验证
cmd.insert(pair<int,string>(1,"ipconfig"));
cmd.insert(pair<int,string>(2,"adb version"));
cmd.insert(pair<int,string>(3,"adb"));
//安全属性的东西
SECURITY_ATTRIBUTES sa;
sa.nLength=sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor=NULL;
sa.bInheritHandle=TRUE;
int ret;
if(!CreatePipe(&hReadPipe1,&hWritePipe1,&sa,0)) //创建两个匿名管道,进行本地的信息传输
{
return -1;
}
if(!CreatePipe(&hReadPipe2,&hWritePipe2,&sa,0))
{
return -1;
}
//启动信息
STARTUPINFO startupinfo = { sizeof(startupinfo) };
PROCESS_INFORMATION pinfo;
//GetStartupInfo(&si);
startupinfo.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
startupinfo.wShowWindow = SW_HIDE;
startupinfo.hStdInput = hReadPipe2;
startupinfo.hStdOutput = startupinfo.hStdError = hWritePipe1;
char cmdLine[256] = {0};
GetSystemDirectory(cmdLine,sizeof(cmdLine)); //获取系统目录
strcat(cmdLine, ("\\cmd.exe")); //找到系统目录下的cmd.exe可执行文件 也就是得到打开它的指令
//char cmdline[]={"C:\\Windows\\System32\\cmd.exe"};//采用直接方法找到目录下的可执行文件 减少可能出现的内存问题
//PROCESS_INFORMATION ProcessInformation; //进程和线程的默认信息 一个结构体
if(CreateProcess(cmdLine,NULL,NULL,NULL,TRUE,0,NULL,NULL,&startupinfo,&pinfo) == FALSE)
{
CloseHandle(hReadPipe2);
CloseHandle(hWritePipe2);
return -1;
}
Sleep(1000); //提供延时 不然可能子进程还没有开启 导致管道数据为空 不准确
memset(Buff,0,sizeof(Buff));
ret=PeekNamedPipe(hReadPipe1,Buff,sizeof(Buff),&lBytesRead,0,0);//管道是否有数据可读 只是用于判断 并不会对管道里的数据修改
if (ret!=1)
{
cout<<"PeekNamedPipe fail"<<endl;
return -1;
}
if (lBytesRead==0)
{
cout<<"管道无数据"<<endl;
}
else{
recvBuff=Buff;
cout<<"管道中有数据:"<<recvBuff;
}
while(iCounter<5){
memset(Buff,0,sizeof(Buff));
strcmd.clear();
//管道中无数据 迭代
map<int ,string>::iterator iter;
//通过按键确认我要进行的哪个指令
int i=0;
//int i=getchar();//输出的其实是acsii码值 1->49 2->50 \n->10
cin>>i;
getchar();//防止换行符的影响
for (iter=cmd.begin();iter!=cmd.end();iter++)
{
if(i==iter->first)
strcmd=iter->second;
}
if (strcmd.length()==0)
{
cout<<"键盘输入的指令简称有误"<<endl;
return -1;
}
ZeroMemory(sendBuff,sizeof(sendBuff));
//复制要发送的字符串
strcpy(sendBuff,strcmd.data());
sendBuff[strlen(strcmd.data())]='\n';//用做在控制台换行符进行指令的操作 不能在这里进行加1 不然会空出一个数组
cout<<"往子进程写入的信息:"<<sendBuff;
//使用strlen 是因为我要写指令的时候已经知道了要写的长度 不必要使用sizeof来包含整个数组大小
if(!WriteFile(hWritePipe2, sendBuff,strlen(sendBuff),&lBytesWrite,NULL))//写入数据 父进程往子进程里写数据
{
cout<<"写入数据失败"<<endl;
return -1;
}
Sleep(1000);//给与子进程运行时间 防止操作过快 子进程还未将信息写入管道
if(!ReadFile(hReadPipe1,Buff,sizeof(Buff),&lBytesRead,NULL)) //从管道1的读句柄 父进程从子进程得到信息 并打印出来
{
cout<<"读取数据失败"<<endl;
return -1;
}
//sizeof 因为要读取的量是未知的 所以使用sizeof 整个5000字符存储区
//读取管道里的数据 采用strlen可能会导致得到的buff值出错
recvBuff=Buff;//Buff数组中是子进程中全部的东西 包括了 输入的命令 和 命令执行的结果
recvBuff=recvBuff.substr(recvBuff.find(sendBuff)+strlen(sendBuff));//除去输入的命令 只保留命令执行的结果
cout<<"从子进程中读取的子进程执行命令后的信息:"<<endl<<recvBuff;
iCounter++;
}
CloseHandle(hReadPipe2);
system("pause");
return EXIT_SUCCESS;
}
运行方面的问题:有可能直接拷贝代码会有一些未定义的错误,可能是因为没有加上using namespace std,很多函数都是在std这个命名空间下的。
1.有可能会出现问题:1>c:\users\mechrevo\documents\visual studio 2013\projects\pipe\pipe\源.cpp(56): error C4996: 'strcat': This function or variable may be unsafe. Consider using strcat_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. 通过网上搜索解决办法,了解出现错误是因为strcat()和strcpy()函数会造成数据溢出不安全。解决方法是:找到【项目】点击最下方的属性,点击【C++】中的【预处理器】,对【预处理器】进行编辑,在里面加入一段代码:_CRT_SECURE_NO_WARNINGS。
注意:需要进行断点测试进行,方便了解数据的流动,如果使用的是adb命令子进程可能运行较慢,所以要加入sleep函数。
代码测试:对于创建子进程之后,通过管道接收的数据,我们会发现在父进程输入命令之前,管道中就已经有数据了,可以读取出来;
继续运行很明显的看到我输入指令代码的传输过程,父进程准备将指令写入管道;
可以看到buff数组缓存区中已经读到了子进程运行指令后的数据,再有recvBuff将接受到的数据进行处理,因为这里可能会出现在子进程中读到由父进程传过去的指令。