天天看点

基于visual c++之windows核心编程代码分析(17)通过pipe进程间通信

管道是一种用于在进程间共享数据的机制,其实质是一段共享内存。Windows系统为这段共享的内存设计采用数据流I/0的方式来访问。由一个进程读、另一个进程写,类似于一个管道两端,因此这种进程间的通信方式称作“管道”。

    管道分为匿名管道和命名管道。

    匿名管道只能在父子进程间进行通信,不能在网络间通信,而且数据传输是单向的,只能一端写,另一端读。

    命令管道可以在任意进程间通信,通信是双向的,任意一端都可读可写,但是在同一时间只能有一端读、一端写。

管道客户端代码实现如下

/* 头文件 */
#include <windows.h> 
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
/* 常量 */
#define BUFSIZE 512
/* ************************************
* int main(VOID) 
* 功能	pipe 通信服务端主函数
**************************************/
int main(int argc, TCHAR *argv[]) 
{ 
	HANDLE hPipe; 
	LPTSTR lpvMessage=TEXT("Default message from client"); 
	TCHAR chBuf[BUFSIZE]; 
	BOOL fSuccess; 
	DWORD cbRead, cbWritten, dwMode; 
	LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\samplenamedpipe"); 

	if( argc > 1 )	// 如果输入了参数,则使用输入的参数
		lpvMessage = argv[1];
	while (1) 
	{ 
		// 打开一个命名pipe
		hPipe = CreateFile( 
			lpszPipename,   // pipe 名 
			GENERIC_READ |   GENERIC_WRITE,		//  可读可写
			0,              // 不共享
			NULL,           // 默认安全属性
			OPEN_EXISTING,  // 已经存在(由服务端创建)
			0,              // 默认属性
			NULL);    
		if (hPipe != INVALID_HANDLE_VALUE) 
			break; 

		// 如果不是 ERROR_PIPE_BUSY 错误,直接退出  
		if (GetLastError() != ERROR_PIPE_BUSY) 
		{
			printf("Could not open pipe"); 
			return 0;
		}

		// 如果所有pipe实例都处于繁忙状态,等待2秒。
		if (!WaitNamedPipe(lpszPipename, 2000)) 
		{ 
			printf("Could not open pipe"); 
			return 0;
		} 
	} 

	// pipe已经连接,设置为消息读状态 
	dwMode = PIPE_READMODE_MESSAGE; 
	fSuccess = SetNamedPipeHandleState( 
		hPipe,    // 句柄
		&dwMode,  // 新状态
		NULL,     // 不设置最大缓存
		NULL);    // 不设置最长时间
	if (!fSuccess) 
	{
		printf("SetNamedPipeHandleState failed"); 
		return 0;
	}

	// 写入pipe
	fSuccess = WriteFile( 
		hPipe,                  // 句柄
		lpvMessage,             // 写入的内容
		(lstrlen(lpvMessage)+1)*sizeof(TCHAR), // 写入内容的长度
		&cbWritten,             // 实际写的内容
		NULL);                  // 非 overlapped 
	if (!fSuccess) 
	{
		printf("WriteFile failed"); 
		return 0;
	}

	do 
	{ 
		// 读回复 
		fSuccess = ReadFile( 
			hPipe,    // 句柄
			chBuf,    // 读取内容的缓存
			BUFSIZE*sizeof(TCHAR),  // 缓存大小
			&cbRead,  // 实际读的字节
			NULL);    // 非 overlapped 

		if (! fSuccess && GetLastError() != ERROR_MORE_DATA) 
			break; //失败,退出

		_tprintf( TEXT("%s\n"), chBuf ); // 打印读的结果
	} while (!fSuccess);  //  ERROR_MORE_DATA 或者成功则循环

	getch();//任意键退出
	// 关闭句柄
	CloseHandle(hPipe);  
	return 0; 
}
           

管道服务端代码实现如下,

/* 头文件 */
#include <windows.h> 
#include <stdio.h>
#include <tchar.h>
/* 常量 */
#define PIPE_TIMEOUT 5000
#define BUFSIZE 4096
/* 结构定义 */
typedef struct 
{ 
	OVERLAPPED oOverlap; 
	HANDLE hPipeInst; 
	TCHAR chRequest[BUFSIZE]; 
	DWORD cbRead;
	TCHAR chReply[BUFSIZE]; 
	DWORD cbToWrite; 
} PIPEINST, *LPPIPEINST; 
/* 函数声明 */
VOID DisconnectAndClose(LPPIPEINST); 
BOOL CreateAndConnectInstance(LPOVERLAPPED); 
BOOL ConnectToNewClient(HANDLE, LPOVERLAPPED); 
VOID GetAnswerToRequest(LPPIPEINST); 
VOID WINAPI CompletedWriteRoutine(DWORD, DWORD, LPOVERLAPPED); 
VOID WINAPI CompletedReadRoutine(DWORD, DWORD, LPOVERLAPPED); 
/* 全局变量 */
HANDLE hPipe; 
/* ************************************
* int main(VOID) 
* 功能	pipe 通信服务端主函数
**************************************/
int main(VOID) 
{ 
	HANDLE hConnectEvent; 
	OVERLAPPED oConnect; 
	LPPIPEINST lpPipeInst; 
	DWORD dwWait, cbRet; 
	BOOL fSuccess, fPendingIO; 

	// 用于连接操作的事件对象 
	hConnectEvent = CreateEvent( 
		NULL,    // 默认属性
		TRUE,    // 手工reset
		TRUE,    // 初始状态 signaled 
		NULL);   // 未命名

	if (hConnectEvent == NULL) 
	{
		printf("CreateEvent failed with %d.\n", GetLastError()); 
		return 0;
	}
	// OVERLAPPED 事件
	oConnect.hEvent = hConnectEvent; 

	// 创建连接实例,等待连接
	fPendingIO = CreateAndConnectInstance(&oConnect); 

	while (1) 
	{
		// 等待客户端连接或读写操作完成 
		dwWait = WaitForSingleObjectEx( 
			hConnectEvent,  // 等待的事件 
			INFINITE,       // 无限等待
			TRUE);       

		switch (dwWait) 
		{ 
		case 0:		
			// pending 
			if (fPendingIO) 
			{ 
				// 获取 Overlapped I/O 的结果
				fSuccess = GetOverlappedResult( 
					hPipe,     // pipe 句柄 
					&oConnect, // OVERLAPPED 结构 
					&cbRet,    // 已经传送的数据量
					FALSE);    // 不等待
				if (!fSuccess) 
				{
					printf("ConnectNamedPipe (%d)\n", GetLastError()); 
					return 0;
				}
			} 

			// 分配内存 
			lpPipeInst = (LPPIPEINST) HeapAlloc(GetProcessHeap(),0,sizeof(PIPEINST)); 
			if (lpPipeInst == NULL) 
			{
				printf("GlobalAlloc failed (%d)\n", GetLastError()); 
				return 0;
			}
			lpPipeInst->hPipeInst = hPipe; 

			// 读和写,注意CompletedWriteRoutine和CompletedReadRoutine的相互调用
			lpPipeInst->cbToWrite = 0; 
			CompletedWriteRoutine(0, 0, (LPOVERLAPPED) lpPipeInst); 

			// 再创建一个连接实例,以响应下一个客户端的连接
			fPendingIO = CreateAndConnectInstance( 
				&oConnect); 
			break; 

			// 读写完成 
		case WAIT_IO_COMPLETION: 
			break; 

		default: 
			{
				printf("WaitForSingleObjectEx (%d)\n", GetLastError()); 
				return 0;
			}
		} 
	} 
	return 0; 
} 

/* ************************************
* CompletedWriteRoutine 
* 	写入pipe操作的完成函数
*	接口参见FileIOCompletionRoutine回调函数定义
*
*	当写操作完成时被调用,开始读另外一个客户端的请求
**************************************/
VOID WINAPI CompletedWriteRoutine(
								  DWORD dwErr, 
								  DWORD cbWritten, 
								  LPOVERLAPPED lpOverLap) 
{ 
	LPPIPEINST lpPipeInst; 
	BOOL fRead = FALSE; 
	// 保存overlap实例
	lpPipeInst = (LPPIPEINST) lpOverLap; 

// 如果没有错误
	if ((dwErr == 0) && (cbWritten == lpPipeInst->cbToWrite)) 
	{		
		fRead = ReadFileEx( 
		lpPipeInst->hPipeInst, 
		lpPipeInst->chRequest, 
		BUFSIZE*sizeof(TCHAR), 
		(LPOVERLAPPED) lpPipeInst, 
		// 写读操作完成后,调用CompletedReadRoutine
		(LPOVERLAPPED_COMPLETION_ROUTINE) CompletedReadRoutine); 
	}	
	if (! fRead) 
		// 出错,断开连接
		DisconnectAndClose(lpPipeInst); 
} 

/* ************************************
* CompletedReadRoutine 
* 	读取pipe操作的完成函数
*	接口参见FileIOCompletionRoutine回调函数定义
*
*	当读操作完成时被调用,写入回复
**************************************/
VOID WINAPI CompletedReadRoutine(
								 DWORD dwErr, 
								 DWORD cbBytesRead, 
								 LPOVERLAPPED lpOverLap) 
{ 
	LPPIPEINST lpPipeInst; 
	BOOL fWrite = FALSE; 

	// 保存overlap实例
	lpPipeInst = (LPPIPEINST) lpOverLap; 

	// 如果没有错误
	if ((dwErr == 0) && (cbBytesRead != 0)) 
	{ 
		// 根据客户端的请求,生成回复
		GetAnswerToRequest(lpPipeInst); 
		// 将回复写入到pipe
		fWrite = WriteFileEx( 
			lpPipeInst->hPipeInst, 
			lpPipeInst->chReply,	//将响应写入pipe
			lpPipeInst->cbToWrite, 
			(LPOVERLAPPED) lpPipeInst, 
			// 写入完成后,调用CompletedWriteRoutine
			(LPOVERLAPPED_COMPLETION_ROUTINE) CompletedWriteRoutine); 
	} 

	if (! fWrite) 
		// 出错,断开连接
		DisconnectAndClose(lpPipeInst); 
} 

/* ************************************
* VOID DisconnectAndClose(LPPIPEINST lpPipeInst) 
* 功能	断开一个连接的实例
* 参数	lpPipeInst,断开并关闭的实例句柄
**************************************/
VOID DisconnectAndClose(LPPIPEINST lpPipeInst) 
{ 
	// 关闭连接实例
	if (! DisconnectNamedPipe(lpPipeInst->hPipeInst) ) 
	{
		printf("DisconnectNamedPipe failed with %d.\n", GetLastError());
	}
	// 关闭 pipe 实例的句柄 
	CloseHandle(lpPipeInst->hPipeInst); 
	// 释放
	if (lpPipeInst != NULL) 
		HeapFree(GetProcessHeap(),0, lpPipeInst); 
} 

/* ************************************
* BOOL CreateAndConnectInstance(LPOVERLAPPED lpoOverlap)
* 功能	建立连接实例
* 参数	lpoOverlap,用于overlapped IO的结构
* 返回值	是否成功
**************************************/
BOOL CreateAndConnectInstance(LPOVERLAPPED lpoOverlap) 
{ 
	LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\samplenamedpipe"); 
	// 创建named pipe	 
	hPipe = CreateNamedPipe( 
		lpszPipename,             // pipe 名 
		PIPE_ACCESS_DUPLEX |      // 可读可写
		FILE_FLAG_OVERLAPPED,     // overlapped 模式 
		// pipe模式
		PIPE_TYPE_MESSAGE |       // 消息类型 pipe 
		PIPE_READMODE_MESSAGE |   // 消息读模式 
		PIPE_WAIT,                // 阻塞模式
		PIPE_UNLIMITED_INSTANCES, // 无限制实例
		BUFSIZE*sizeof(TCHAR),    // 输出缓存大小
		BUFSIZE*sizeof(TCHAR),    // 输入缓存大小
		PIPE_TIMEOUT,             // 客户端超时
		NULL);                    // 默认安全属性
	if (hPipe == INVALID_HANDLE_VALUE) 
	{
		printf("CreateNamedPipe failed with %d.\n", GetLastError()); 
		return 0;
	}

	// 连接到新的客户端
	return ConnectToNewClient(hPipe, lpoOverlap); 
}

/* ************************************
* BOOL ConnectToNewClient(HANDLE hPipe, LPOVERLAPPED lpo)
* 功能	建立连接实例
* 参数	lpoOverlap,用于overlapped IO的结构
* 返回值	是否成功
**************************************/
BOOL ConnectToNewClient(HANDLE hPipe, LPOVERLAPPED lpo) 
{ 
	BOOL fConnected, fPendingIO = FALSE; 

	// 开始一个 overlapped 连接 
	fConnected = ConnectNamedPipe(hPipe, lpo); 

	if (fConnected) 
	{
		printf("ConnectNamedPipe failed with %d.\n", GetLastError()); 
		return 0;
	}
	switch (GetLastError()) 
	{ 
		// overlapped连接进行中. 
	case ERROR_IO_PENDING: 
		fPendingIO = TRUE; 
		break; 
		// 已经连接,因此Event未置位 
	case ERROR_PIPE_CONNECTED: 
		if (SetEvent(lpo->hEvent)) 
			break; 
		// error
	default: 
		{
			printf("ConnectNamedPipe failed with %d.\n", GetLastError());
			return 0;
		}
	} 
	return fPendingIO; 
}

// TODO根据客户端的请求,给出响应
VOID GetAnswerToRequest(LPPIPEINST pipe)
{
	_tprintf( TEXT("[%d] %s\n"), pipe->hPipeInst, pipe->chRequest);
	lstrcpyn( pipe->chReply,  TEXT("Default answer from server") ,BUFSIZE);
	pipe->cbToWrite = (lstrlen(pipe->chReply)+1)*sizeof(TCHAR);
}