WindowsAPI 查阅笔记:进程间管道通信

进程间有名管道的通信:

1.1 重叠I/O(Overlapped I/O)

重叠I/O(Overlapped I/O)是Windows编程中的一种异步 I / O 处理方式,它允许程序在发出I/O请求后继续执行其他任务,而不必等待I/O操作完成。这种机制通过使用OVERLAPPED结构体和相关的异步I/O函数(如ReadFileEx、WriteFileEx等)来实现。

在重叠I/O中,OVERLAPPED结构体用于存储I/O操作的上下文信息。它包含以下成员:

typedef struct _OVERLAPPED {
    ULONG_PTR   Internal;		//一个指向内部缓冲区的指针。
    ULONG_PTR   InternalHigh;	//内部缓冲区的高位地址。
    HANDLE      hEvent;			//一个用于异步操作的事件对象。
} OVERLAPPED, *LPOVERLAPPED;

当程序发出一个异步I/O请求时,它会将OVERLAPPED结构体的地址作为参数传递给异步I/O函数。异步I/O函数会在后台执行I/O操作,并在操作完成时通过设置事件对象或完成端口来通知程序。程序可以在任何时间点检查事件对象或完成端口的状态,以确定I/O操作是否已完成,并处理操作的结果。

重叠I/O通常用于需要高性能和响应能力的应用程序,如数据库服务器、文件服务器等。它允许这些应用程序在等待I/O操作完成的同时处理其他任务,从而提高系统的吞吐量和整体性能。

请注意,使用重叠I/O需要小心处理错误和资源管理。程序必须确保在异步I/O操作完成后正确地关闭事件对象和句柄,以避免资源泄漏和其他潜在问题。此外,程序还需要正确处理异步I/O操作的结果,包括检查错误代码和处理读取或写入的数据。

1.2 异步 I/O

同步 I / O ,指的是调用 ReadFile、WriteFile 等函数进行输入输出时候,系统完全执行该函数后才继续执行接下来的代码。
异步 I / O ,指的是调用 ReadFile、WriteFile 等函数之后,函数立即返回,线程可以进行其他操作。剩下的I/O操作再系统内核中自动完成。
再系统内核完成出入输出后,程序如何知道I/O已经完成:通过【完全函数】

1.3 完全函数

【完全函数】,如果使用 ReadFileEx、WriteFileEx等进行 I/O ,可以指定完全函数,所谓【完全函数】指的是内核完成 I/O 之后,内核会自动回调这个函数。当【完全函数】被调用时候,就指明内核已经完成了 I/O,程序可以再这个函数中进行一个 I/O 完成后需要的操作(例如释放内存)。

1.3 代码

  • 服务端创建了一个异步I/O管道,

  • 服务端 CompletedWriteRoutine 和 CompletedReadRoutine 两个函数互为对方的【完全代码】

  • 服务端 一开始创建了,一个【事件】,客户端连接成功后打开,否则等待。

  • 客户端通过管道名连接有名管道。

服务端:

#include <windows.h>
#include <cstdio>
#include <tchar.h>
#include <stdlib.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;

/***********
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;
				//读和写,注意CompleteWriteRoutine 和 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;
}

 
/*************************
建立连接实例
返回值 是否成功 
**********************/ 
BOOL CreateAndConnectInstance(LPOVERLAPPED lpoOverlap)
{
//	LPTSTR lpszPipename = _T("\\\\.\\pipe\\samplenamedpipe");
	TCHAR lpszPipename[64] = TEXT("\\\\.\\pipe\\samplenamedpipe");
	//创建 named pipe
	hPipe = CreateNamedPipe(
		lpszPipename,			//pipe名 
		
		PIPE_ACCESS_DUPLEX | 	//可读可写
		FILE_FLAG_OVERLAPPED,	//重叠I/O 模式
		//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)
{
	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("ConnectNamePipe failed with %d.\n",
				GetLastError());
			return 0;
		}
	}
	
	return fPendingIO;
} 

/*************************
写入pipe操作的完成函数
当写操作完成时候被调用,开始读另一个请求 
*************************/

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,
			//写操作完成后,调用CompleteReadRoutine
			(LPOVERLAPPED_COMPLETION_ROUTINE) CompletedReadRoutine); 
	}
	if(! fRead){
		//出错 断开连接
		DisconnectAndClose(lpPipeInst); 
	}
	return ;
}

/*************************
读取pipe操作的完成函数
当写操作完成时候被调用,开始读另一个请求 
*************************/

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,
			lpPipeInst->cbToWrite,
			(LPOVERLAPPED) lpPipeInst,
			//写入完成后,调用CompleteWriteRoutine
			(LPOVERLAPPED_COMPLETION_ROUTINE) CompletedWriteRoutine); 
	}
	if(! fWrite){
		//出错 断开连接
		DisconnectAndClose(lpPipeInst); 
	}
	return ;
}

//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);
	return ;
} 

/********************
功能 断开一个连接的实例 
*******************/

VOID DisconnectAndClose(LPPIPEINST lpPipeInst)
{
	//关闭连接实例
	if(! DisconnectNamedPipe(lpPipeInst->hPipeInst))
	{
		printf("DisconnectNamePipe failed with %d.\n",
			GetLastError()); 
	} 
	//关闭 pipe 实例的句柄
	CloseHandle(lpPipeInst->hPipeInst);
	//释放
	if(lpPipeInst != NULL){
		HeapFree(GetProcessHeap(), 0, lpPipeInst);
	} 
	return ;
}

客户端:

//PipeClnt.cpp

/**********************
通过 pipe 进行进程间通信 
*********************/

#include <windows.h>
#include <cstdio>
#include <conio.h>
#include <tchar.h>

//常量 
#define BUFSIZE 512

/***********************
pipe 通信服务端主函数 
**********************/
int main(int argc, TCHAR *argv[])
{
	HANDLE hPipe;
	LPTSTR lpvMessage = TEXT("Default message from client");
//	TCHAR lpvMessage[64] = TEXT("Default message from client");
	TCHAR chBuf[BUFSIZE];
	BOOL fSuccess;
	DWORD cbRead, cbWritten, dwMode;
//	LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\samplenamedpipe");
	TCHAR lpszPipename[64] = 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 实例都处于繁忙,等待2s
		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("SetNamePipeHandleState 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);
	
	//任意键退出 
	getch(); 
	//关闭句柄
	CloseHandle(hPipe); 
	return 0;
}
 

先启动服务端,再启动多个客户端。
运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值