zoukankan      html  css  js  c++  java
  • 21、Windows API 进程间通信,管道(Pipe)

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

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

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

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

    一、注意点

    1、常用API

    Pipes[2]

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

    2、示例

    1)服务器端

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

    CreateNamedPipe

    ConnectNamedPipe

    ReadFile/WriteFile

    DisconnectNamedPipe

    示例代码

    通过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

    示例代码

    通过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

  • 相关阅读:
    js-异步机制与同步机制
    js-正则表达式
    js-注释代码习惯
    布局-块级元素水平垂直居中
    js-本地调试跨域
    vue2-项目资源收集
    git 忽略文件夹
    运行npm run eject报错解决方法
    柯里化函数
    常用正则表达式总结
  • 原文地址:https://www.cnblogs.com/mydomain/p/1931914.html
Copyright © 2011-2022 走看看