您的位置:首页 > 其它

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

2015-03-05 14:11 344 查看

转载自:Windows API 进程间通信,管道(Pipe)

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

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

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

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

一、注意点

1、常用API

Pipes[2]

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

2、示例

1)服务器端

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

CreateNamedPipe

ConnectNamedPipe

ReadFile/WriteFile

DisconnectNamedPipe

示例代码





通过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()); 
        return0;
    }
    // OVERLAPPED 事件
    oConnect.hEvent = hConnectEvent; 

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

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

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

            // 分配内存 
            lpPipeInst = (LPPIPEINST) HeapAlloc(GetProcessHeap(),0,sizeof(PIPEINST)); 
            if (lpPipeInst == NULL) 
            {
                printf("GlobalAlloc failed (%d)\n", GetLastError()); 
                return0;
            }
            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()); 
                return0;
            }
        } 
    } 
    return0; 
} 

/* ************************************
* 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()); 
        return0;
    }

    // 连接到新的客户端
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()); 
        return0;
    }
    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());
            return0;
        }
    } 
    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

示例代码





通过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"); 
            return0;
        }

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

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

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

    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);  
    return0; 
}




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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: