转载自:Windows API 进程间通信,管道(Pipe)
管道是一种用于在进程间共享数据的机制,其实质是一段共享内存。Windows系统为这段共享的内存设计采用数据流I/0的方式来访问。由一个进程读、另一个进程写,类似于一个管道两端,因此这种进程间的通信方式称作“管道”。
管道分为匿名管道和命名管道。
匿名管道只能在父子进程间进行通信,不能在网络间通信,而且数据传输是单向的,只能一端写,另一端读。
命令管道可以在任意进程间通信,通信是双向的,任意一端都可读可写,但是在同一时间只能有一端读、一端写。
一、注意点
1、常用API
Pipes[2]
在[3,4]中也对这一部分进行了介绍。
2、示例
1)服务器端
创建管道 >> 监听 >> 读写 >> 关闭
CreateNamedPipe
ConnectNamedPipe
ReadFile/WriteFile
DisconnectNamedPipe
示例代码
![](http://static.oschina.net/uploads/img/201503/25101252_cqqQ.gif)
![复制代码](http://static.oschina.net/uploads/img/201503/25101252_iA6w.gif)
通过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); }
![复制代码](http://static.oschina.net/uploads/img/201503/25101252_iA6w.gif)
2)客户端
打开命令管道,获得句柄 >> 写入数据 >> 等待回复
WaitNamedPipe
SetNamedPipeHandleState
示例代码
![](http://static.oschina.net/uploads/img/201503/25101252_cqqQ.gif)
![复制代码](http://static.oschina.net/uploads/img/201503/25101252_iA6w.gif)
通过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 ; }
![复制代码](http://static.oschina.net/uploads/img/201503/25101252_iA6w.gif)
3、I/O简介
I/O模式不仅在进程间通信时使用,任何具有数据流形式的输入输出(包括文件输入输出、内核通信、网络输入输出等)都涉及I/O模式。
异步( Asynchronous)和同步(Synchronous) I/O是两种基本的I/O模式。
同步I/O
所谓同步I/O是指在调用ReadFile、WriteFile等函数进行输入输出操作时,系统完成了输入输出ReadFile、WriteFile才返回。在操作系统进行I/O操作的过程上,用户态线程不能执行,因此在同步I/O时,如果需要在I/O时进行其他操作就只能再开启线程。
异步I/O
异步I/O是在调用ReadFile、WriteFile等函数后,函数立即返回,线程可以进行其他操作。剩下的I/O操作在系统内核中自动完成。那么在系统内核完成输入输出后,程序如何知道I/O是否已完成?
一种方法,称作完成函数(Completion Routines),如果使用ReadFileEx、WriteFileEx等进行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