首页 > 技术文章 > Windows进程通信之一看就懂的匿名管道通信

iBinary 2019-05-30 14:01 原文

进程通信之一看就懂的匿名管道通信

一丶匿名管道

1.1何为匿名管道

匿名管道是用来父进程跟子进程通信的.还有一种是命名管道.不需要父子进程就可以进行通信的.今天先说匿名管道的.
匿名管道. 就是父进程创建子进程. 读取子进程数据.或者给子进程发送数据.当然子进程也可以给父进程发送数据.以及读取父进程发送过来的数据.

1.2创建匿名管道需要注意的事项

创建匿名管道,首先你要明白什么是管道. 管道你可以想象成一个管子.
我们通过这个管子发送数据.
如下图所示:

通过上图,我们就知道其实创建了两个管道. 分别是父进程读取的管道.以及
子进程读取的管道.相应的.子进程也可以对父进程读取的管道进行传输数据.父进程就可以读取了.
这段话可能难以理解.你可以这样想.

我父进程读取子进程使用第一个管道. 那么反正子进程写的话也是使用第一个管道.因为子进程写.我们父进程才能读.

1.3 创建匿名管道需要的步骤

首先你需要了解创建匿名管道的API

WINBASEAPI
BOOL
WINAPI
CreatePipe(
    _Out_ PHANDLE hReadPipe,
    _Out_ PHANDLE hWritePipe,
    _In_opt_ LPSECURITY_ATTRIBUTES lpPipeAttributes,
    _In_ DWORD nSize
    );

关于这个API很简单.相比你过来看匿名管道.说明你已经知道这个API了.
只不过不知道管道概念.(我猜的);

这个API很简单.就是创建管道. 两个句柄.读取句柄跟写入句柄.
我们需要创建两个管道.

还需要注意的是,父进程创建子进程,必须让父进程句柄可继承.这个就是第三个属性.安全属性描述符中给即可.

那么说一下创建步骤吧.

1.创建一个安全属性描述符,设置句柄可继承
2.创建两个管道. 父读->子写 子读->父写的
3.重定向输出,将子进程的读 以及子进程的写重定向.
4.创建子进程
5.读取\写入数据给子进程.

1.4代码例子

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>





int main()
{

	HANDLE hParentRead, hParentWrite, hChildRead, hChildWrite; //创建4个句柄

	STARTUPINFO si = { 0 };							//启动信息结构体
	si.cb = sizeof(si);
	PROCESS_INFORMATION pi = { 0 };                 //进程信息结构体

	DWORD dwWritedBytes = 0;
	DWORD dwReadedBytes = 0;

	DWORD dwBytesRead = 0;
	DWORD dwTotalBytesAvail = 0;
	DWORD dwBytesLeftThisMessage = 0;

	SECURITY_ATTRIBUTES sa = { 0 };				   //安全属性描述符		
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	sa.bInheritHandle = TRUE;                      //设置句柄可继承

	//创建管道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,
		"cmd.exe",                      //创建cmd进程.默认寻找cmd进程.
		NULL,
		NULL,
		TRUE,
		CREATE_NO_WINDOW,
		NULL,
		NULL,
		&si,
		&pi);

	char szBuffer[15] = "calc \n";
	WriteFile(hParentWrite, szBuffer, 15, NULL, 0);//使用writeFile操作管道,给cmd发送数据命令.
	return 0;
}

1.5代码运行截图

二丶匿名管道读取写入问题

2.1 简介

匿名管道是阻塞的.一个命令的输入那么ReadFile才能读取. 且还会阻塞. 所以要使用API PeekNamedPipe 来查看管道数据.
而且要写死循环去读. 但是我们不知道读取多大.所以建立一个一分钟的线程来读取.

2.2 C++代码

.h

#pragma once
#include "IUtileTools.h"
//IUtileTools 是工具类 包含了字符串大小写转换 获取路径等自己用的.可以删除.
class CPipeOpt : public IUtileTools
{
public:
	CPipeOpt();
	~CPipeOpt();

public:
	void CreateChildProcess();
	void WriteToPipe(char* szCommand, DWORD commandsize);
	string ReadFromPipe();
private:
	HANDLE g_hChildStd_IN_Rd = NULL;
	HANDLE g_hChildStd_IN_Wr = NULL;
	HANDLE g_hChildStd_OUT_Rd = NULL;
	HANDLE g_hChildStd_OUT_Wr = NULL;
	PROCESS_INFORMATION piProcInfo;
};


.cpp

#include "CPipeOpt.h"
#define BUFSIZE 4096 



CPipeOpt::CPipeOpt()
{
	
	SECURITY_ATTRIBUTES saAttr;

	

	saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
	saAttr.bInheritHandle = TRUE;
	saAttr.lpSecurityDescriptor = NULL;

	
	if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
		return;

	// Ensure the read handle to the pipe for STDOUT is not inherited.

	if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
		return;

	// Create a pipe for the child process's STDIN. 

	if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
		return;

	// Ensure the write handle to the pipe for STDIN is not inherited. 

	if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
		return;

	// Create the child process. 

	CreateChildProcess();


}

CPipeOpt::~CPipeOpt()
{
	CloseHandle(piProcInfo.hProcess);
	CloseHandle(piProcInfo.hThread);
	CloseHandle(g_hChildStd_OUT_Wr);
	CloseHandle(g_hChildStd_IN_Rd);
	TerminateProcess(piProcInfo.hProcess, 0);
}

void CPipeOpt::CreateChildProcess()
// Create a child process that uses the previously created pipes for STDIN and STDOUT.
{
	TCHAR szCmdline[] = TEXT("cmd.exe");
	
	STARTUPINFO siStartInfo;
	BOOL bSuccess = FALSE;

	// Set up members of the PROCESS_INFORMATION structure. 

	ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));

	// Set up members of the STARTUPINFO structure. 
	// This structure specifies the STDIN and STDOUT handles for redirection.

	ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
	siStartInfo.cb = sizeof(STARTUPINFO);
	siStartInfo.hStdError = g_hChildStd_OUT_Wr;
	siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
	siStartInfo.hStdInput = g_hChildStd_IN_Rd;
	siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
	siStartInfo.dwFlags |= STARTF_USESHOWWINDOW;
	//siStartInfo.dwFlags = STARTF_USESTDHANDLES;

	// Create the child process. 

	bSuccess = CreateProcess(NULL,
		szCmdline,     // command line 
		NULL,          // process security attributes 
		NULL,          // primary thread security attributes 
		TRUE,          // handles are inherited 
		NULL,             // creation flags 
		NULL,          // use parent's environment 
		NULL,          // use parent's current directory 
		&siStartInfo,  // STARTUPINFO pointer 
		&piProcInfo);  // receives PROCESS_INFORMATION 
}

void CPipeOpt::WriteToPipe(char *szCommand,DWORD commandsize)

// Read from a file and write its contents to the pipe for the child's STDIN.
// Stop when there is no more data. 
{
	
	DWORD dwWritten = 0;
	
	BOOL bSuccess = FALSE;
	
	
	WriteFile(g_hChildStd_IN_Wr, szCommand, commandsize, &dwWritten, NULL);
		
	WaitForSingleObject(g_hChildStd_IN_Wr, INFINITE);
}

DWORD WINAPI WaitThread(LPVOID)
{
	Sleep(1 * 60 * 1000);
	return 0;
}

string CPipeOpt::ReadFromPipe()
{
	DWORD dwRead = 0;
    DWORD dwAvail = 4096;
	char* start = nullptr; //记录起始地址
	char cbBuf[BUFSIZE] = { 0 };
	BOOL bSuccess = FALSE;
	string strRes = "";
	HANDLE hThread = CreateThread(nullptr, 0, WaitThread, nullptr, 0, 0);
	while (TRUE) 
	{
		DWORD dwByteRecv;
		DWORD dwTotalBytesAvail;
		while (PeekNamedPipe(
			g_hChildStd_OUT_Rd, 
			cbBuf, 
			BUFSIZE,
			&dwByteRecv,
			&dwTotalBytesAvail,
			nullptr))
		{
			if (dwByteRecv <= 0) {
				break;
			}
			DWORD dwAllocSize(dwTotalBytesAvail + 1);
			PBYTE lpReadBuffer = new BYTE[dwAllocSize];
			ReadFile(g_hChildStd_OUT_Rd, lpReadBuffer,
				dwTotalBytesAvail, &dwByteRecv, nullptr);
			lpReadBuffer[dwByteRecv] = '\0';
			strRes += reinterpret_cast<LPCSTR>(lpReadBuffer);

			delete[] lpReadBuffer;
			lpReadBuffer = nullptr;
		}

		if ( WAIT_OBJECT_0 == WaitForSingleObject(piProcInfo.hProcess, 0)
			|| WAIT_OBJECT_0 == WaitForSingleObject(hThread, 0))
		{
			break;
		}
	}
	return strRes;
}

推荐阅读