【学习笔记3】管道通信:匿名管道

一、前言

上篇笔记(【学习笔记2】管道通信:输入输出重定向)记录了输入输出重定向的实现方法,本文总结一下匿名管道的内容,希望对自己与各位有所帮助。

二、管道的概念

简单理解,计算机中的管道(pipe)类似现实世界中的水管,从一端放入水流,另一端就会流出来。回到计算机中,这个水流就是数据。

管道(pipe)又分为 匿名管道 和 命名管道。本文主要介绍如何使用匿名管道来实现输入输出重定向功能。

三、匿名管道的创建和使用

3.1 函数原型

3.1.1 CreatePipe

/* 创建管道 */
BOOL WINAPI CreatePipe(
  PHANDLE hReadPipe,                      // 管道输出(读取)端句柄
  PHANDLE hWritePipe,                     // 管道输入(写入)端句柄
  LPSECURITY_ATTRIBUTES lpPipeAttributes, // 管道的安全属性
  DWORDnSize                              // 管道缓冲区容量,设置0时使用默认大小
);

管道的安全属性通常设置如下:

SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;

注意:不能在管道的输出端写入数据,也不能在管道的输入端读取数据。

3.1.2 ReadFile

/* 从管道中读取数据 */
BOOL ReadFile(
  HANDLE hFile,                // 管道输出端(读取)句柄
  LPVOID lpBuffer,             // 数据缓冲区指针,读取的数据将写在该缓冲区中
  DWORD nNumberOfBytesToRead,  // 指定读取的字节数
  LPDWORD lpNumberOfBytesRead, // 返回实际读取到的字节数
  LPOVERLAPPED lpOverlapped    // 用于异步操作,一般传入NULL即可
);

3.1.3 WriteFile

/* 向管道写入数据 */
BOOL WriteFile(
  HANDLE hFile,                   // 管道输入端(写入)句柄,也可以是CreateFile()接口创建的文件句柄
  LPVOID lpBuffer,                // 待写入管道的数据缓冲区指针
  DWORD nNumberOfBytesToWrite,    // 指定写入的字节数
  LPDWORD lpNumberOfBytesWritten, // 返回实际写入管道的字节数
  LPOVERLAPPED lpOverlapped       // 用于异步操作,一般传入NULL即可
);

3.1.4 CloseHandle

/* 关闭管道端口句柄 */
BOOL CloseHandle(
  HANDLE hObject  // 想要关闭的句柄
);

注意:当管道输入输出端都被关闭后,系统会自动关闭该管道,并回收相关资源。

3.2 管道使用技巧

从以上管道相关的函数原型来看,管道的读写其实和文件的读写非常类似,实际上管道也是一种特殊的文件——内存映射文件。

使用管道时需要注意:读取和写入数据时,一定要注意顺序,如果管道中没有数据,调用ReadFile()会造成阻塞,直到有其它线程将数据写入管道。同样,当有线程正在管道中读取数据时,其它试图将数据写入管道的的线程也会被阻塞,更多内容请查阅MSDN。

3.3 示例代码

此处以【学习笔记2】管道通信:输入输出重定向中的示例程序作为子进程,在父进程中创建两个管道(输入/输出 管道),分别用于存放输入输出数据,将这两个管道重定向到输入输出文件(stdin.txt 和 stdout.txt)。

此时,在父进程中启动子进程,将子进程的标准输入输出改成从上述创建的 输入/输出 管道句柄,即实现了子进程的输入输出重定向,示例代码如下:

#include <windows.h>
#include <stdio.h>

int main()
{
    char sz[3][50] = { "示例程序.exe", "infile.txt", "outfile.txt" };
    HANDLE hPipeInputRead = NULL;
    HANDLE hPipeInputWrite = NULL;
    HANDLE hPipeOutputRead = NULL;
    HANDLE hPipeOutputWrite = NULL;

    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(sa);
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = NULL;

    CreatePipe(&hPipeInputRead, &hPipeInputWrite, &sa, 0);   // 创建输入管道
    CreatePipe(&hPipeOutputRead, &hPipeOutputWrite, &sa, 0); // 创建输出管道

    HANDLE hInputFile = CreateFile(L"stdin.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
    HANDLE hOutputFile = CreateFile(L"stdout.txt", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    CHAR Buffer[1024 * 4] = { 0 };
    DWORD dwNumberOfBytesRead = 0;
    DWORD dwNumberOfBytesWritten = 0;
    BOOL  bRet =  FALSE;

    while (TRUE) { // 从文件stdin.txt中读取数据,写到输入管道中
        bRet = ReadFile(hInputFile, Buffer, sizeof(Buffer), &dwNumberOfBytesRead, NULL);
        if (!bRet || dwNumberOfBytesRead == 0)
            break;
        bRet = WriteFile(hPipeInputWrite, Buffer, dwNumberOfBytesRead, &dwNumberOfBytesWritten, NULL);
        if (!bRet)
            break;
    }
    // 关闭输入管道的写句柄
    CloseHandle(hInputFile);
    CloseHandle(hPipeInputWrite);

    // 启动demo.exe作为子进程
    STARTUPINFO si;
    si.cb = sizeof(si);
    GetStartupInfo(&si);
    si.hStdInput = hPipeInputRead;    // 输入由标准输入 -> 从管道中读取
    si.hStdOutput = hPipeOutputWrite; // 输出由标准输出 -> 输出到管道
    si.wShowWindow = SW_HIDE;
    si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    PROCESS_INFORMATION pi;

    CreateProcess(L"demo.exe", NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
    WaitForSingleObject(pi.hProcess, INFINITE);

    // 关闭进程相关句柄
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
    CloseHandle(hPipeInputRead);
    CloseHandle(hPipeOutputWrite);

    while (TRUE) { // 从输出管道中读取数据,写到文件stdout.txt中
        bRet = ReadFile(hPipeOutputRead, Buffer, sizeof(Buffer), &dwNumberOfBytesRead, NULL);
        if (!bRet || dwNumberOfBytesRead == 0)
            break;
        bRet = WriteFile(hOutputFile, Buffer, dwNumberOfBytesRead, &dwNumberOfBytesWritten, NULL);
        if (!bRet)
            break;
    }

    //关闭输出管道的读句柄
    CloseHandle(hOutputFile);
    CloseHandle(hPipeOutputRead);

    return 0;
}

四、总结

从上述示例来看,匿名管道可以在不修改程序源码的情况下实现输入输出重定向,也就是说只要有exe文件,即可实现输入输出重定向,并且上述示例中,父进程也可以使用 输入/输出 管道写入或读取数据,即实现了父子进程间的通信。

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容