进程间有名管道的通信:
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");ll
// 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;
}
先启动服务端,再启动多个客户端。
运行结果: