4.1实验目的
通过在VS中编程,实现父子进程的管道通信,要求父进程从窗口中输入数字,输入的数字通过管道传输给子进程,子进程将输入的字符串进行相加求和,同时打开readme.txt文件计算完成后读取文档中的一行,最后以 和_文档某行字符串的形式进行管道通信返回给父进程。EG:
父进程DOS输入:45
子进程求和得:4+5=9
文档当前字符串:Just a pipe test!
父进程DOS显示:9_ Just a pipe test!
4.2实验要点
由于管道单向通信的特点,因此父进程和子进程进行通信的过程中需要创建两个管道,一个读管道,一个写管道。
注意缓冲区大小的设置,不当的缓冲区大小设置会导致数据的丢失。
每次将父进程从子进程读取的数据进行显示时,都需要对句柄进行重定向,否则会导致打印失败。std_write = GetStdHandle(STD_OUTPUT_HANDLE);
匿名管道只能实现本地进程间的通信,跨网络的进程间的通信无法通过匿名管道实现的。
匿名管道采用半双工的方式通信,且通信的进程间是有血缘关系的,及父子关系或者兄弟关系进程,由此可知并不是本地计算机上任意两个进程就能通过匿名管道进行通信。
匿名管道有个重要的功能就是可以通过匿名管道的来实现子进程输出的的重定向。
画了很多红线的这个区域中的信息来自那里呢?为什么会在这个文本框中输出呢?其实这就可以通过匿名管道来实现,在卸载 QQ 游戏这幅截图中呢,其实运行了两个进程,一个就是我们看到的这个输出了图形界面的进程,我们称之为卸载表象进程(父进程),而另外一个用来执行真正意义上的卸载的进程我们称之为卸载实质进程(子进程)。其实该卸载表象进程在其执行过程中创建了卸载实质进程来执行真正的卸载操作,而后,卸载实质进程会输出上面用红色矩形标记的区域中的信息,如果我们使用默认的输出的话,卸载实质进程会将上面红色区域标记中的信息输出到默认的黑框框中,但是我们可以使用匿名管道来更改卸载实质进程的输出,让其将输出数据输入到匿名管道中,而后卸载表象进程从匿名管道中读取到这些输出数据,然后再将这些数据显示到卸载表象进程的文本框中就可以了。而上面的这种用来更改卸载实质进程的输出的技术就称之为输出重定向。当然与之相对的还有输入重定向的。我们可以让一个进程的输入来自于匿名管道,而不是我们在黑框框中输入数据。
4.3运行效果
4.4代码
父进程
#include<iostream>
#include <Windows.h>
#include<string>
using namespace std;
#define BUFFSIZE 200 // 缓冲区大小
// 父进程
class ParentPip
{
public:
ParentPip();
~ParentPip();
void InitPip();
void PipRead();
void PipWrite();
char Writes[BUFFSIZE]; // 写出去
char Reads[BUFFSIZE]; // 读回来
private:
HANDLE hParentRead; // 父进程读句柄
HANDLE hParentWrite; // 父进程写句柄
HANDLE hChildRead; // 子进程写句柄
HANDLE hChildWrite; // 子进程读句柄
HANDLE std_write; // Dos 句柄
SECURITY_ATTRIBUTES sa = {0}; //管道安全属性
STARTUPINFO si = {0}; //用于决定新进程的主窗体如何显示
PROCESS_INFORMATION pi = {0};//进程信息结构体
};
ParentPip::ParentPip()
{
hParentRead = NULL;
hParentWrite = NULL;
hChildRead = NULL;
hChildWrite = NULL;
std_write = NULL;
//ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); // 用 0 填充
//ZeroMemory(&si, sizeof(STARTUPINFO)); // 用 0 填充
//ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); // 用 0 填充
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE; //设置句柄可继承
si.cb = sizeof(si);
}
ParentPip::~ParentPip()
{
}
void ParentPip::InitPip()
{
//创建管道1. 父进程读 -> 子进程写入
BOOL bRet = CreatePipe(&hParentRead,
&hChildWrite,
&sa,
0);
//创建管道2. 子进程读->父进程写.
bRet = CreatePipe(&hChildRead,
&hParentWrite,
&sa,
0);
//这里将子进程写重定向到 stdout中. 子进程读取重定向到stdinput中
si.hStdInput = hChildRead;
si.hStdOutput = hChildWrite;
si.dwFlags = STARTF_USESTDHANDLES; //设置窗口隐藏启动
bRet = CreateProcess(NULL,
"..\\..\\Child\\Debug\\Child.exe", //创建cmd进程.默认寻找cmd进程.
NULL,
NULL,
TRUE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi);
}
void ParentPip::PipWrite()
{
string input;
cin >> input;
input = input + "/0";
strcpy_s(Writes, &input[0]);
DWORD dwWrite;
bool wsuccess = false;
wsuccess = WriteFile(hParentWrite, Writes, strlen(Writes), &dwWrite, NULL);
if (wsuccess == 0 || dwWrite == 0) {
cout << "写管道失败" << endl;
}
}
void ParentPip::PipRead()
{
DWORD dwRead;
DWORD dwWrite;
HANDLE std_write;
bool rsuccess = false;
std_write = GetStdHandle(STD_OUTPUT_HANDLE);
rsuccess = ReadFile(hParentRead, Reads, BUFFSIZE, &dwRead, NULL);
if (!rsuccess || dwRead == 0)
{
cout << "读管道失败" << endl;
}
rsuccess = WriteFile(std_write, Reads, dwRead, &dwWrite, NULL); // 写到标准输出,即dos窗口
if (!rsuccess)cout << "输出失败" << endl;
}
int main()
{
ParentPip mypip;
mypip.InitPip();
int sum = 10;
while (sum--)
{
mypip.PipWrite();
mypip.PipRead();
}
return 0;
}
子进程
#include<iostream>
#include <Windows.h>
#include<string>
#include<fstream>
using namespace std;
#define BUFFSIZE 200 // 缓冲区大小
// 子进程
class ChildPip
{
public:
ChildPip();
~ChildPip();
void InitPip();
void PipRead();
void PipWrite();
void openfile();
int MyAdd(DWORD dwRead);
char Writes[BUFFSIZE]; // 写出去
char Reads[BUFFSIZE]; // 读回来
private:
HANDLE hRead; // 继承的读管道
HANDLE hWrite; // 继承的写管道
ifstream commandList; // 上上级目录
string line;
HANDLE hChildRead; // 子进程写句柄
HANDLE hChildWrite; // 子进程读句柄
HANDLE std_write; // Dos 句柄
SECURITY_ATTRIBUTES sa = { 0 }; //管道安全属性
STARTUPINFO si = { 0 }; //用于决定新进程的主窗体如何显示
PROCESS_INFORMATION pi = { 0 };//进程信息结构体
};
ChildPip::ChildPip()
{
openfile();
}
ChildPip::~ChildPip()
{
}
void ChildPip::InitPip()
{
hRead = GetStdHandle(STD_INPUT_HANDLE); // 输入重定向
hWrite = GetStdHandle(STD_OUTPUT_HANDLE); // 输出重定向
if ((hRead == INVALID_HANDLE_VALUE) || (hRead == INVALID_HANDLE_VALUE))
cout << "继承句柄无效" << endl;
}
void ChildPip::PipRead()
{
DWORD dwRead;
bool rsuccess = false;
rsuccess = ReadFile(hRead, Reads, sizeof(Reads), &dwRead, NULL); //使用标准输入句柄(即匿名管道读句柄)读取数据
int sum = 0;
getline(commandList, line);
int index = 0;
int k = MyAdd(dwRead - 2);
Writes[k++] = '_';
while (index < line.size()) {
Writes[k] = line[index];
index++;
k++;
}
Writes[k++] = '\n';
Writes[k++] = '\r';
Writes[k] = '\0';
}
void ChildPip::PipWrite()
{
DWORD dwWrite;
bool wsuccess = false;
wsuccess = WriteFile(hWrite, Writes, strlen(Writes), &dwWrite, NULL);
if (!wsuccess)cout << "输出失败" << endl;//使用标准输出句柄(即匿名管道写句柄)写入数据
}
void ChildPip::openfile()
{
commandList.open("..\\..\\数据\\readme.txt");
if (!commandList.is_open()) {
cout << "打开文件失败" << endl;
}
}
int ChildPip::MyAdd(DWORD dwRead)
{
int ret = 0;
int temp = 0;
for (int i = 0; i < dwRead; i++) {
temp += Reads[i] - '0';
}
while (temp > 0) {
Writes[ret] = temp % 10 + '0';
temp /= 10;
ret++;
}
int left = 0, right = ret - 1;
char tp;
while (left < right)
{
tp = Writes[left];
Writes[left] = Writes[right];
Writes[right] = tp;
left++; right--;
}
return ret;
}
int main()
{
ChildPip mypip;
mypip.InitPip();
printf("\n*******\nabcd\n");
int sum = 10;
while (sum--)
{
mypip.PipRead();
mypip.PipWrite();
}
system("pause");
return 0;
}