21、Windows API 进程间通信,管道(Pipe)

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

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

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

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

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

一、注意点

1、常用API

Pipes[2]

[3,4]中也对这一部分进行了介绍。

2、示例

1)服务器端

创建管道 >> 监听 >> 读写 >> 关闭

CreateNamedPipe

ConnectNamedPipe

ReadFile/WriteFile

DisconnectNamedPipe

示例代码

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif 通过pipe进程间通信-服务器端
 
   
通过pipe进程间通信
**************************************/
/* 头文件 */
#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);
}

2)客户端

打开命令管道,获得句柄 >> 写入数据 >> 等待回复

WaitNamedPipe

SetNamedPipeHandleState

示例代码

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif 通过pipe进程间通信-客户端
 
   
通过pipe进程间通信
**************************************/
/* 头文件 */
#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 ;
}

3I/O简介

    I/O模式不仅在进程间通信时使用,任何具有数据流形式的输入输出(包括文件输入输出、内核通信、网络输入输出等)都涉及I/O模式。

    异步( Asynchronous)和同步(Synchronous) I/O是两种基本的I/O模式

同步I/O

所谓同步I/O是指在调用ReadFileWriteFile等函数进行输入输出操作时,系统完成了输入输出ReadFileWriteFile才返回。在操作系统进行I/O操作的过程上,用户态线程不能执行,因此在同步I/O时,如果需要在I/O时进行其他操作就只能再开启线程。

异步I/O

    异步I/O是在调用ReadFileWriteFile等函数后,函数立即返回,线程可以进行其他操作。剩下的I/O操作在系统内核中自动完成。那么在系统内核完成输入输出后,程序如何知道I/O是否已完成?

    一种方法,称作完成函数(Completion Routines),如果使用ReadFileExWriteFileEx等进行I/O,可以指定完成函数,所谓完成函数是指内核在完成I/O后,内核会回调这个函数。当完成函数被调用时,就指明内核已经完成了I/O,程序可以在这个函数中进行一个I/O完成后需要的操作(例如释放内存)

参考

[1] 精通Windows API 函数、接口、编程实例

[2] http://msdn.microsoft.com/en-us/library/aa365137%28VS.85%29.aspx

[3] http://www.cnblogs.com/mydomain/archive/2010/09/18/1830452.html

[4] http://www.cnblogs.com/mydomain/archive/2010/09/04/1818266.html

目录
相关文章
|
4天前
|
人工智能 测试技术 API
Windows用户必备:Postman v11详细安装指南与API测试入门教程(附官网下载
Postman是全球领先的API开发与测试工具,支持REST、SOAP、GraphQL等协议调试。2025年最新版v11新增AI智能生成测试用例、多环境变量同步等功能,适用于前后端分离开发、自动化测试、接口文档自动生成及团队协作共享API资源。本文详细介绍Postman的软件定位、核心功能、安装步骤、首次配置、基础使用及常见问题解答,帮助用户快速上手并高效利用该工具进行API开发与测试。
|
2月前
|
消息中间件 存储 网络协议
从零开始掌握进程间通信:管道、信号、消息队列、共享内存大揭秘
本文详细介绍了进程间通信(IPC)的六种主要方式:管道、信号、消息队列、共享内存、信号量和套接字。每种方式都有其特点和适用场景,如管道适用于父子进程间的通信,消息队列能传递结构化数据,共享内存提供高速数据交换,信号量用于同步控制,套接字支持跨网络通信。通过对比和分析,帮助读者理解并选择合适的IPC机制,以提高系统性能和可靠性。
235 14
|
2月前
|
监控 搜索推荐 开发工具
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
186 2
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
|
4月前
|
存储 Unix Linux
进程间通信方式-----管道通信
【10月更文挑战第29天】管道通信是一种重要的进程间通信机制,它为进程间的数据传输和同步提供了一种简单有效的方法。通过合理地使用管道通信,可以实现不同进程之间的协作,提高系统的整体性能和效率。
|
5月前
|
开发框架 .NET API
Windows Forms应用程序中集成一个ASP.NET API服务
Windows Forms应用程序中集成一个ASP.NET API服务
131 9
|
5月前
|
Java 关系型数据库 MySQL
java控制Windows进程,服务管理器项目
本文介绍了如何使用Java的`Runtime`和`Process`类来控制Windows进程,包括执行命令、读取进程输出和错误流以及等待进程完成,并提供了一个简单的服务管理器项目示例。
74 1
|
6月前
|
网络协议 API Windows
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾
|
6月前
|
SQL 网络协议 数据库连接
已解决:连接SqlServer出现 provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程【C#连接SqlServer踩坑记录】
本文介绍了解决连接SqlServer时出现“provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程”错误的步骤,包括更改服务器验证模式、修改sa用户设置、启用TCP/IP协议,以及检查数据库连接语句中的实例名是否正确。此外,还解释了实例名mssqlserver和sqlserver之间的区别,包括它们在默认设置、功能和用途上的差异。
|
6月前
|
消息中间件 Unix Linux
C语言 多进程编程(二)管道
本文详细介绍了Linux下的进程间通信(IPC),重点讨论了管道通信机制。首先,文章概述了进程间通信的基本概念及重要性,并列举了几种常见的IPC方式。接着深入探讨了管道通信,包括无名管道(匿名管道)和有名管道(命名管道)。无名管道主要用于父子进程间的单向通信,有名管道则可用于任意进程间的通信。文中提供了丰富的示例代码,展示了如何使用`pipe()`和`mkfifo()`函数创建管道,并通过实例演示了如何利用管道进行进程间的消息传递。此外,还分析了管道的特点、优缺点以及如何通过`errno`判断管道是否存在,帮助读者更好地理解和应用管道通信技术。
|
7月前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
111 0

热门文章

最新文章