C++开发基础之命名管道用法详解含示例程序

发布时间:2024年01月24日

前言

命名管道(Named Pipe)是一种在操作系统中用于进程间通信的机制。它允许不相关的进程通过共享一个命名的管道来交换数据。命名管道可以在本地计算机上的进程之间进行通信,也可以在不同计算机上的进程之间进行跨网络通信。

命名管道的创建和使用类似于传统的匿名管道(Anonymous Pipe),但有一些重要的区别。首先,命名管道是具有唯一名称的,而匿名管道是无名的临时管道。其次,命名管道可以在不同的进程之间进行通信,而匿名管道仅限于父子进程或相关进程之间的通信。

命名管道在操作系统中以文件的形式存在,被视为特殊类型的文件。每个命名管道都有一个唯一的名称,其他进程可以通过该名称来打开和使用管道。通常,命名管道的名称使用特定的命名约定,如 “\.\pipe\pipename” 的形式。

命名管道区别于匿名管道

命名管道(Named Pipe)和匿名管道(Anonymous Pipe)是用于进程间通信的两种不同机制,它们在以下几个方面有所区别:

  1. 唯一名称 vs 无名临时:命名管道具有唯一的名称,其他进程可以通过该名称来打开和使用管道。而匿名管道是临时的,没有名称,只能在相关进程(如父子进程)之间使用。

  2. 进程间通信范围:命名管道可以在不相关的进程之间进行通信,包括在本地计算机上的进程和跨网络的进程。匿名管道仅限于在相关进程(如父子进程)之间进行通信。

  3. 持久性:命名管道在创建后会持久存在,直到被显式删除。而匿名管道在关闭所有相关的句柄后会自动销毁。

  4. 文件系统中的表示:命名管道在操作系统中以文件的形式存在,被视为特殊类型的文件。而匿名管道不在文件系统中表示,是由操作系统内部维护的。

  5. 句柄传递:命名管道支持句柄传递,即一个进程可以将一个已经打开的句柄传递给另一个进程。而匿名管道不支持句柄传递。

命名管道更适用于不相关进程之间的通信,具有持久性和跨网络的能力,而匿名管道更适用于相关进程(如父子进程)之间的通信,是一种临时的机制。

命名管道适用场景

命名管道是一种简单且有效的进程间通信机制,可用于不同场景下的应用程序。它提供了一种方便的方式来实现进程之间的数据交换和共享资源。命名管道(Named Pipe)适用于以下场景:

  1. 客户端-服务器通信:命名管道可以用于实现客户端和服务器之间的通信。服务器创建一个命名管道并等待客户端连接,客户端可以通过打开命名管道并进行读写操作来与服务器进行通信。

  2. 多进程间通信:当多个进程需要进行数据交换或共享资源时,可以使用命名管道来进行进程间通信。每个进程可以通过命名管道发送和接收数据,实现数据的传输和共享。

  3. 跨平台通信:命名管道不仅限于在单个操作系统内部使用,还可以用于跨平台通信。不同操作系统上的进程可以通过命名管道进行通信,实现跨平台的数据交换。

  4. 客户端和服务端分离:如果你有一个需要运行在不同计算机上的客户端和服务端应用程序,可以使用命名管道进行通信。客户端和服务端可以通过网络连接,并使用命名管道进行数据传输。

  5. 多线程间通信:在一个多线程的应用程序中,线程之间可能需要进行数据交换和同步。命名管道可以作为线程间通信的一种方式,不同线程可以通过命名管道发送和接收数据。

需要注意的是,命名管道是一种同步的通信机制,因此读写操作可能会阻塞进程的执行。如果需要实现异步通信,可以考虑使用其他的IPC机制,如消息队列或共享内存。

命名管道常规用法

namedpipeapi.h 是 Windows API 中的一个头文件,它包含了一些用于操作命名管道(Named Pipe)的函数和常量。下面是 namedpipeapi.h 中几个常用函数的详细介绍:

  1. CreateNamedPipe:

    HANDLE CreateNamedPipe(
        LPCTSTR lpName,
        DWORD dwOpenMode,
        DWORD dwPipeMode,
        DWORD nMaxInstances,
        DWORD nOutBufferSize,
        DWORD nInBufferSize,
        DWORD nDefaultTimeOut,
        LPSECURITY_ATTRIBUTES lpSecurityAttributes
    );
    

    该函数用于创建一个命名管道。

    • lpName: 命名管道的名称。
    • dwOpenMode: 管道的打开模式,指定管道可以被打开的方式,如 PIPE_ACCESS_DUPLEX 表示管道是双向的。
    • dwPipeMode: 管道的模式,指定管道的读取模式和等待模式,如 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT 表示管道以字节为单位进行读取,并且是阻塞等待模式。
    • nMaxInstances: 管道允许的最大实例数。
    • nOutBufferSizenInBufferSize: 管道的输出缓冲区大小和输入缓冲区大小。
    • nDefaultTimeOut: 默认的超时时间。
    • lpSecurityAttributes: 安全属性。
  2. ConnectNamedPipe:

    BOOL ConnectNamedPipe(
        HANDLE hNamedPipe,
        LPOVERLAPPED lpOverlapped
    );
    

    该函数用于等待客户端连接到命名管道。

    • hNamedPipe: 命名管道的句柄。
    • lpOverlapped: 指向 OVERLAPPED 结构的指针,用于异步 I/O 操作。
  3. ReadFile:

    BOOL ReadFile(
        HANDLE hFile,
        LPVOID lpBuffer,
        DWORD nNumberOfBytesToRead,
        LPDWORD lpNumberOfBytesRead,
        LPOVERLAPPED lpOverlapped
    );
    

    该函数用于从命名管道中读取数据。

    • hFile: 命名管道的句柄。
    • lpBuffer: 存放读取数据的缓冲区。
    • nNumberOfBytesToRead: 想要读取的字节数。
    • lpNumberOfBytesRead: 实际读取的字节数。
    • lpOverlapped: 指向 OVERLAPPED 结构的指针,用于异步 I/O 操作。
  4. WriteFile:

    BOOL WriteFile(
        HANDLE hFile,
        LPCVOID lpBuffer,
        DWORD nNumberOfBytesToWrite,
        LPDWORD lpNumberOfBytesWritten,
        LPOVERLAPPED lpOverlapped
    );
    

    该函数用于向命名管道中写入数据。

    • hFile: 命名管道的句柄。
    • lpBuffer: 要写入的数据。
    • nNumberOfBytesToWrite: 要写入的字节数。
    • lpNumberOfBytesWritten: 实际写入的字节数。
    • lpOverlapped: 指向 OVERLAPPED 结构的指针,用于异步 I/O 操作。
  5. DisconnectNamedPipe:

    BOOL DisconnectNamedPipe(
        HANDLE hNamedPipe
    );
    

    该函数用于断开与命名管道的连接。

    • hNamedPipe: 命名管道的句柄。

这些函数是使用 namedpipeapi.h 头文件中提供的命名管道API的基本操作。它们允许你创建、连接、读取、写入和断开命名管道,从而实现进程间通信。

命名管道进行通信的基本步骤

在C++中使用命名管道进行通信的基本步骤:

1. 包含必要的头文件:

#include <iostream>
#include <fstream>
#include <string>

2. 创建或打开命名管道:

const char* pipeName = "\\.\pipe\myPipe";  // 命名管道的名称
HANDLE pipeHandle = CreateNamedPipe(pipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, NULL);
if (pipeHandle == INVALID_HANDLE_VALUE) {
    std::cout << "Failed to create named pipe." << std::endl;
    return 1;
}

3. 等待其他进程连接到管道:

BOOL isConnected = ConnectNamedPipe(pipeHandle, NULL);
if (!isConnected) {
    std::cout << "Failed to connect to named pipe." << std::endl;
    CloseHandle(pipeHandle);
    return 1;
}

4. 从管道中读取数据:

char buffer[4096];
DWORD bytesRead;
BOOL isSuccess = ReadFile(pipeHandle, buffer, sizeof(buffer), &bytesRead, NULL);
if (isSuccess) {
    std::string message(buffer, bytesRead);
    std::cout << "Received data: " << message << std::endl;
}

5. 向管道中写入数据:

std::string data = "Hello from the writer!";
DWORD bytesWritten;
BOOL isSuccess = WriteFile(pipeHandle, data.c_str(), data.length(), &bytesWritten, NULL);
if (isSuccess) {
    std::cout << "Data sent successfully." << std::endl;
}

6. 关闭管道:

CloseHandle(pipeHandle);

命名管道双向通信示例

命名管道(Named Pipe)允许不相关的进程之间进行双向通信,其中一个进程充当管道的读取端,另一个进程充当管道的写入端。

下面这个示例,展示了如何在客户端和服务器之间进行双向通信:

服务器端代码:

#include <iostream>
#include <Windows.h>

int main()
{
    HANDLE pipe;
    DWORD bytesRead;
    char buffer[1024];

    // 创建命名管道
    LPCWSTR pipeName = L"\\\\.\\pipe\\MyPipe";

    pipe = CreateNamedPipe(pipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
        PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, NULL);

    if (pipe == INVALID_HANDLE_VALUE)
    {
        std::cout << "Failed to create named pipe. Error code: " << GetLastError() << std::endl;
        return 1;
    }

    // 等待客户端连接
    std::cout << "Waiting for client connection..." << std::endl;
    if (ConnectNamedPipe(pipe, NULL))
    {
        std::cout << "Client connected!" << std::endl;

        // 从客户端读取数据
        while (ReadFile(pipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL))
        {
            buffer[bytesRead] = '\0';
            std::cout << "Received from client: " << buffer << std::endl;

            // 向客户端发送响应
            const char* response = "Hello from server!";
            WriteFile(pipe, response, strlen(response) + 1, NULL, NULL);
        }
    }

    // 关闭管道
    CloseHandle(pipe);

    std::cout << "Press any key to exit...";
    std::cin.get();  // 等待用户按下任意键

    return 0;
}

运行结果
在这里插入图片描述

客户端代码:

#include <iostream>
#include <Windows.h>

int main()
{
    HANDLE pipe;
    DWORD bytesWritten;
    char buffer[1024];

    LPCWSTR pipeName = L"\\\\.\\pipe\\MyPipe";
    // 连接到命名管道
    pipe = CreateFile(pipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

    if (pipe == INVALID_HANDLE_VALUE)
    {
        std::cout << "Failed to connect to named pipe. Error code: " << GetLastError() << std::endl;
        return 1;
    }

    // 向服务器发送数据
    const char* message = "Hello from client!";
    WriteFile(pipe, message, strlen(message) + 1, &bytesWritten, NULL);
    std::cout << "Sent to server: " << message << std::endl;

    // 从服务器接收响应
    while (ReadFile(pipe, buffer, sizeof(buffer) - 1, &bytesWritten, NULL))
    {
        buffer[bytesWritten] = '\0';
        std::cout << "Received from server: " << buffer << std::endl;
        break;
    }

    // 关闭管道
    CloseHandle(pipe);

    std::cout << "Press any key to exit...";
    std::cin.get();  // 等待用户按下任意键

    return 0;
}

运行结果
在这里插入图片描述

在这个示例中,服务器端创建了一个命名管道,并等待客户端的连接。一旦客户端连接成功,服务器端就开始读取来自客户端的数据,并向客户端发送响应。

客户端通过CreateFile函数连接到服务器端的命名管道,并向服务器发送一条消息。然后,客户端从服务器端接收响应并打印出来。

参考文档

文章来源:https://blog.csdn.net/houbincarson/article/details/135755627
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。