如何在 Windows 中使用 c++ 创建命名管道,以便可以在给定管道名称的情况下从中读取数据,就好像它是常规文件一样?

How can I create a named pipe with c++ in Windows so that data can be read from it as if it were a regular file, given the pipe's name?

提问人:prsm 提问时间:11/8/2023 更新时间:11/10/2023 访问量:125

问:

问题

由于一些不寻常的要求,我希望能够从 Windows 中的 c++ 代码中从其名称打开命名管道,就好像它是常规的“文件路径”一样,而不是使用 .ReadFile

法典

我已经编写了服务器和客户端代码,以便在 Windows 中从命名管道中读取和写入。

服务器代码

#include <iostream>
#include <windows.h>

int main()
{
    // Client will block if not in a thread.
    const TCHAR *pipe_name = TEXT("\\\\.\\pipe\\Pipe");

    HANDLE hPipe;
    char buffer[1024];
    DWORD dwRead;

    hPipe = CreateNamedPipe(pipe_name,
                            PIPE_ACCESS_DUPLEX,
                            PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
                            1,
                            1024 * 16,
                            1024 * 16,
                            NMPWAIT_USE_DEFAULT_WAIT,
                            NULL);

    if (hPipe == INVALID_HANDLE_VALUE)
    {
        std::cout << "CreateNamedPipe failed, GLE=" << GetLastError() << std::endl;
        return -1;
    }

    // This will block if there's no client.
    if (ConnectNamedPipe(hPipe, NULL) != FALSE)
    {
        // Read from the pipe
        DWORD bytesRead = 0;
        BOOL success = ReadFile(hPipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL);

        if (!success || bytesRead == 0)
        {
            std::cerr << "ReadFile failed, error: " << GetLastError() << std::endl;
        }
        else
        {
            buffer[bytesRead] = '\0'; // null terminate
            std::cout << "Received: " << buffer << std::endl;
        }
    }

    DisconnectNamedPipe(hPipe);
    CloseHandle(hPipe);

客户端代码

请注意,我已将管道名称用作“文件路径”,而不是更常见的,但它可以随心所欲地工作:fstreamWriteFile

#include <fstream>
#include <iostream>
#include <windows.h>

int main()
{
    const TCHAR *pipe_name = TEXT("\\\\.\\pipe\\Pipe");

    std::cout << "Running client. This uses fstream..." << std::endl;

    while (true)
    {
        if (WaitNamedPipe(pipe_name, NMPWAIT_WAIT_FOREVER) != FALSE)
        {
            break;
        }
        else
        {
            std::cout << "Waiting for pipe server..." << std::endl;
            Sleep(1000);
        }
    }

    std::fstream fs;
    fs.open(pipe_name, std::fstream::out); // Works!

    if (fs.is_open())
    {
        fs << "Hello, Pipe Server!";
        fs.close();
        std::cout << "Client is done." << std::endl;

        return 0;
    }
    else
    {
        std::cout << "Failed to open the pipe." << std::endl;

        return 1;
    }
}

Client Code, 相关应用

如下所示的替代客户端代码也有效。

#include <fstream>
#include <iostream>
#include <windows.h>

int main()
{
    const TCHAR *pipe_name = TEXT("\\\\.\\pipe\\Pipe");

    std::cout << "Running client. This uses WriteFile..." << std::endl;

    while (true)
    {
        if (WaitNamedPipe(pipe_name, NMPWAIT_WAIT_FOREVER) != FALSE)
        {
            break;
        }
        else
        {
            std::cout << "Waiting for pipe server..." << std::endl;
            Sleep(1000);
        }
    }

    HANDLE hPipe;
    DWORD dwWritten;

    hPipe = CreateFile(pipe_name,
                       GENERIC_READ | GENERIC_WRITE,
                       0,
                       NULL,
                       OPEN_EXISTING,
                       0,
                       NULL);

    if (hPipe != INVALID_HANDLE_VALUE)
    {
        WriteFile(hPipe,
                  "Hello, Pipe Server!",
                  strlen("Hello, Pipe Server!") + 1,
                  &dwWritten,
                  NULL);
        CloseHandle(hPipe);
        std::cout << "Client is done." << std::endl;
        
        return 0;
    }
    else
    {
        std::cout << "Failed to connect to pipe server, GLE=" << GetLastError() << std::endl;

        return 1;
    }
}

所有版本均在 Visual Studio Code 下按预期编译和运行,使用 .cl.exe /Zi /EHsc /Fe: <file>.exe <file>.cpp

输出

使用代码的客户端版本和“文件路径”,我获得以下输出:fstream

服务器优先

首先启动,在客户端启动之前不输出:

Received: Hello, Pipe Server!
Press any key to continue . . .

客户 下一页

接下来开始,立即输出:

Running client. This uses fstream...
Client is done.
Press any key to continue . . .

值得一提的是,颠倒执行顺序也会产生良好的结果。

客户至上

首先启动,立即输出等待消息,并在服务器启动后输出成功消息:

Running client. This uses fstream...
Waiting for pipe server...
Waiting for pipe server...
Waiting for pipe server...
Client is done.
Press any key to continue . . .

服务器 下一个

立即输出:

Received: Hello, Pipe Server!
Press any key to continue . . .

总结

问题是我想使用被视为常规文件路径的管道名称来读取数据,而不是传统的 .我能够使用这种方法将数据写入管道,但读取失败。具有讽刺意味的是(并认为将其描述为情境讽刺实际上可能是正确的),我并不真正关心数据如何写入管道 - 我很乐意使用它,但我真的很想使用管道的名称,就好像它是读取数据的常规文件路径一样。ReadFileWriteFile

我试过了什么

我尝试了如下所示的服务器代码的变体,并结合了两个版本的客户端代码。

尝试的服务器代码

#include <iostream>
#include <fstream>
#include <string>
#include <windows.h>

int main()
{
    // Client will block if not in a thread.
    const TCHAR *pipe_name = TEXT("\\\\.\\pipe\\Pipe");

    HANDLE hPipe;
    char buffer[1024];
    DWORD dwRead;

    hPipe = CreateNamedPipe(pipe_name,
                            PIPE_ACCESS_DUPLEX,
                            PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
                            1,
                            1024 * 16,
                            1024 * 16,
                            NMPWAIT_USE_DEFAULT_WAIT,
                            NULL);

    if (hPipe == INVALID_HANDLE_VALUE)
    {
        std::cout << "CreateNamedPipe failed, GLE=" << GetLastError() << std::endl;
        return -1;
    }

    // This will block if there's no client.
    if (ConnectNamedPipe(hPipe, NULL) != FALSE)
    {
        std::ifstream fs;
        fs.open(pipe_name, std::fstream::in);

        if (fs.is_open())
        {
            std::string line;
            while (std::getline(fs, line))
            {
                std::cout << "Received: " << line << std::endl;
            }
            fs.close();
        }
        else
        {
            std::cout << "Server failed to open the pipe with ifstream." << std::endl;
        }

    }

    DisconnectNamedPipe(hPipe);
    CloseHandle(hPipe);

    return 0;
}

变种

主要是绝望,没有太多的思考。我尝试使用而不是只是 ,与将管道类型从 to 更改为 结合使用和不将其组合在一起,但无济于事。最后,我还尝试打开with和with。没有骰子。所有这些尝试都产生了以下输出。PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPEDPIPE_ACCESS_DUPLEXPYPE_TYPE_BYTE | PIPE_READMODE_BYTEPIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGEfstreamstd::fstream::in | std::fstrem::outstd::fstream::binary

新输出

在客户端,我的输出与以前相同。在服务器端,我尝试了一切,我得到了

Server failed to open the pipe with ifstream.
Press any key to continue . . .

事实上,与我想要的几乎完全相同的对应物可以写入数据,这让我希望可以使其用于读取数据,但是,当然,我完全意识到这并不能保证。

其他潜在的解决方案(以及为什么它们对我不起作用)

Read Named Pipe with _lread 中有人声称可以执行我正在寻找的操作,但我无法控制管道事务的执行方式,除了它将被视为常规文件以读取数据。_lopen

尽管有相反的说法(Fifo 文件 Windows 示例),但我找不到如何在 Windows 中执行 -equivalent 的真实示例(使用 istream 从命名管道读取)。mkfifo

C++ WinAPI 命名管道

评论

1赞 user4581301 11/8/2023
XY回答:当我需要做这样的事情时,通常是为了与其他平台兼容。我将抽象出接口背后的问题,以便客户端代码始终得到它想要的东西,并且实现可能因平台而异,无论是条件编译还是更智能的构建脚本。例如,Windows 实现与 POSIX 实现完全不同,两者都隐藏在后端的某个地方。
0赞 user207421 11/8/2023
读取失败是怎么回事?
0赞 Jerry Coffin 11/8/2023
如果要像流一样读取/写入命名管道,请创建一个流缓冲区,用于在其下溢/溢出函数中读取和写入命名管道。下面是这样编写的回显服务器和客户端示例:godbolt.org/z/bM8a5WPPY。为了进行测试,您需要运行两个实例,一个实例带有命令行参数,它会来回发送少量数据。首先启动服务器 - 我没有包含允许先启动客户端的额外循环,但如果需要,您可以重新添加它。server
0赞 user207421 11/8/2023
fs.open(pipe_name, std::fstream::in);在服务器中没有意义。你正在成为你自己的客户。服务器需要通过 执行其 I/O。hPipe
0赞 prsm 11/8/2023
@user207421,读取失败,因为检查在服务器中返回 false。fs.is_open()

答:

0赞 prsm 11/10/2023 #1

下面的服务器/客户端代码可以与我之前尝试的代码进行对比,本着 @user207421 建议的精神,它是对我有用的解决方案。

服务器

#include <iostream>
#include <fstream>
#include <string>
#include <windows.h>

int main()
{
    const TCHAR *pipe_name = TEXT("\\\\.\\pipe\\Pipe");
    HANDLE hPipe;
    DWORD dwRead;

    hPipe = CreateNamedPipe(pipe_name,
                            PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
                            PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
                            1,
                            1024,
                            1024,
                            NMPWAIT_USE_DEFAULT_WAIT,
                            NULL);

    if (hPipe == INVALID_HANDLE_VALUE)
    {
        std::cout << "CreateNamedPipe failed, GLE=" << GetLastError() << std::endl;
        return -1;
    }

    // This will block if there's no client.
    if (ConnectNamedPipe(hPipe, NULL) != FALSE)
    {
        DWORD dwRead;
        char buffer[20];
        if (!ReadFile(hPipe, buffer, sizeof(buffer) - 1, &dwRead, NULL))
        {
            std::cout << "ReadFile failed, GLE=" << GetLastError() << std::endl;
        }
        else
        {
            buffer[dwRead] = '\0';
            std::cout << "Received: " << buffer << std::endl;
        }

        DWORD dwWritten;
        WriteFile(hPipe,
                  "Hello, Pipe Client!",
                  strlen("Hello, Pipe Client!") + 1,
                  &dwWritten,
                  NULL);
        std::cout << "Server is done." << std::endl;
    }

    DisconnectNamedPipe(hPipe);
    CloseHandle(hPipe);

    return 0;
}

客户

#include <fstream>
#include <iostream>
#include <string>
#include <windows.h>

int main()
{
    const TCHAR *pipe_name = TEXT("\\\\.\\pipe\\Pipe");

    while (true)
    {
        if (WaitNamedPipe(pipe_name, NMPWAIT_WAIT_FOREVER) != FALSE)
        {
            break;
        }
        else
        {
            std::cout << "Waiting for pipe server..." << std::endl;
            Sleep(1000);
        }
    }

    std::fstream fs;
    fs.open(pipe_name, std::fstream::in);
    if (fs.is_open())
    {
        fs << "Hello, Pipe Server!" << std::endl;
        std::string line;
        while (std::getline(fs, line))
        {
            std::cout << "Received: " << line << std::endl;
        }
        fs.close();

        return 0;
    }
    else
    {
        std::cout << "Server failed to open the pipe with ifstream." << std::endl;

        return 1;
    }
}

输出

先启动服务器,再启动客户端

服务器

Received: Hello, Pipe Client!
Press any key to continue . . .

客户

Received: Hello, Pipe Server!
Server is done.
Press any key to continue . . .

先启动客户端,再启动服务器

客户

Waiting for pipe server...
Waiting for pipe server...
Received: Hello, Pipe Client!
Press any key to continue . . .

服务器

Received: Hello, Pipe Server!
Server is done.
Press any key to continue . . .