最近实现一个远程超级终端的功能,通信模式是这样的:

客户端------通过网络发送cmd命令到------服务器端------通过进程间通信(管道),将此cmd命令发给-----cmd.exe程序,cmd.exe执行此cmd命令

接下来:

cmd.exe------程序将执行结果返回------服务器端------发送此次结果到------客户端,客户端对结果进行显示

其中服务器端程序和cmd.exe程序之间的通信设计到了进程间的通信,采用采用匿名双管道来完成。

管道1:一端:服务器端写数据,另一端:cmd.exe读数据

管道2:一端:服务器端读数据,另一端:cmd.exe写数据

匿名管道的3个特点:

1、只能实现本地机器上的两个进程之间的通信,所以服务器端程序和cmd.exe程序需在一台机器上

2、匿名管道通常用来在父进程和子进程之间进行通信,所以两个进程如果想要具有父子关系,必须通过父进程调用CreateProcess函数去启动子进程

3、因为匿名管道没有名称,所以只能在父进程调用CreateProcess调用子进程时,将管道的读、写句柄传递给子进程

 

以此例来说明匿名管道的进程间通信如何编码:

1、在服务器程序中创建管道1和2,创建成功后循环读取管道1中的数据,并将读取到的数据发送给客户端:

 

void CShellInfo::SheelRead()
{
	SECURITY_ATTRIBUTES Sa1;
	Sa1.nLength=sizeof(SECURITY_ATTRIBUTES);
	Sa1.lpSecurityDescriptor=NULL;
	Sa1.bInheritHandle=TRUE;//必须设置为TRUE,这样子进程可以继承父进程的句柄
	if (!CreatePipe(&m_hReadFile,&m_hWritePipe,&Sa1,0))
	{
		printf("CreatePipeError_1\n");
		return;
	}

	SECURITY_ATTRIBUTES Sa2;
	Sa2.nLength=sizeof(SECURITY_ATTRIBUTES);
	Sa2.lpSecurityDescriptor=NULL;
	Sa2.bInheritHandle=TRUE;//必须设置为TRUE,这样子进程可以继承父进程的句柄
	if (!CreatePipe(&m_hReadPipe,&m_hWriteFile,&Sa2,0))
	{
		printf("CreatePipeError_2\n");
		return;
	}

	/*读取管道数据,发送给远程主机*/
	char szBuf[1024]={0};
	DWORD dwBytes=0;
	while (TRUE)
	{
		if (ReadFile(m_hReadFile,szBuf,1023,&dwBytes,NULL)==0)
		{
			printf("ReadFileError\n");
			break;
		}

		CMD cmd;
		cmd.Flag=0;
		strcpy_s(cmd.Command,sizeof(cmd.Command),szBuf);

		CMsgHead MsgHead(COMMAND,sizeof(CMD));
		int nRetH=SendData(m_ShellSock,(char*)&MsgHead,sizeof(MsgHead));
		int nRetB=SendData(m_ShellSock,(char*)&cmd,sizeof(CMD));
		if (nRetH<=0||nRetB<=0)
		{
			printf("SendPipeDataError\n");
			break;
		}
	}
}

 

 

 

 

 

其中:

CreatePipe()第三个参数是一个指向SECURITY_ATTRIBUTES结构体的指针,检测返回的句柄能否被子进程继承。以前凡是需要SECURITY_ATTRIBUTES指针的地方,我们都设为NULL,此处这个指针不能再设置为NULL了,因为匿名管道只能在父子进程之间进行通信,且管道是父进程创建的,父进程自然能获得管道的句柄,子进程如果想要获得匿名管道的句柄,只能从父进程继承而来。当父进程和子进程都拥有了匿名管道的句柄后,父子进程之间就可以进行通信了。所以此处我们应该定义一个SECURITY_ATTRIBUTES结构体变量,并将此结构体变量的第三个参数设置为TRUE,TRUE表示父进程返回的句柄可以被子进程继承。

2、创建一个和父进程通信的子进程:

 

void CShellInfo::SheelInit()
{
	m_Si.cb = sizeof(STARTUPINFO);
	GetStartupInfo(&m_Si);
	m_Si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
	m_Si.hStdInput   = m_hReadPipe;
	m_Si.hStdOutput  = m_hWritePipe;
	m_Si.hStdError   = m_hWritePipe;
	m_Si.wShowWindow = SW_HIDE;

	TCHAR szCmdPath[_MAX_PATH]={0};
	GetSystemDirectory(szCmdPath,MAX_PATH);
	wcscat_s(szCmdPath,_T("\\cmd.exe"));

	/*创建子进程*/
	if (!CreateProcess(szCmdPath,NULL,NULL,NULL,TRUE,NULL,NULL,NULL,&m_Si,&m_Pi))
	{
		printf("CreateProcessError\n");
		return;
	}

	CloseHandle( m_Pi.hProcess );
	CloseHandle( m_Pi.hThread );
}


其中:CreateProcess()的第一个参数是:一个指向NULL终止的字符串,用来指定可执行文件的名称,最好是一个完整路径;倒数第二个参数m_Si:一个启动结构STARTUPINFO的指针 ,主要用来指定新进程(子进程)的窗口如何显示,使用此结构体应该首先为cb成员赋值,表示结构体本身的大小,以字节为单位。其次为dwFlag成员赋值,表明在什么情况下选择使用该结构体的哪些成员;最后一个参数m_Pi:指向PROCESS_INFORMATION结构体的指针,用来接收新进程的标识信息:进程句柄、主线程句柄、全局进程标识符、全局线程标识符。

 

补充:当调用CreateProcess()启动一个子进程时,他将继承父进程所有可继承的已经打开的句柄,但在子进程中无法知道他所继承的句柄中哪一个是管道的读句柄,哪一个是管道的写句柄,为了让子进程从众多句柄中区分出管道的读、写句柄,必须将子进程的特殊句柄设置为管道的读、写句柄,这里我们将子进程的标准输入句柄和标准输出句柄分别设置为管道的读、写句柄,这样,子进程只要得到了标准的输入和输出句柄就得到了管道的读写句柄。

3、服务器端(父进程)向管道中写数据:

 

void CShellInfo::SheelWrite(char* szBuf)
{
	DWORD dwBytes=0;
	WriteFile(m_hWriteFile,szBuf,strlen(szBuf),&dwBytes,NULL);
	if (strcmp(szBuf,"exit\r\n")==0)
	{
		CloseHandle(m_hReadPipe);
		CloseHandle(m_hWritePipe);
		CloseHandle(m_hReadFile);
		CloseHandle(m_hWriteFile);
	}
}


通信完成后,客户端结果显示如下:

 

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐