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

问题描述 投票:0回答:1

问题

由于一些不寻常的要求,我希望能够从 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);

客户端代码

请注意,我使用了

fstream
和管道名称作为“文件路径”,而不是更常见的
WriteFile
,但它按我想要的方式工作:

#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;
    }
}

客户端代码,替代方案

替代客户端代码(如下所示)也可以工作。

#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;
    }
}

所有版本都使用

cl.exe /Zi /EHsc /Fe: <file>.exe <file>.cpp
在 Visual Studio Code 下按预期编译和运行。

输出

使用

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 . . .

总结

问题是我想使用被视为常规文件路径的管道名称来读取数据,而不是传统的

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

我尝试过的

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

尝试的服务器代码

#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;
}

变体

主要是绝望,没有太多思考。我尝试使用

PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED
而不是仅使用
PIPE_ACCESS_DUPLEX
创建命名管道,无论是否将其与将管道类型从
PYPE_TYPE_BYTE | PIPE_READMODE_BYTE
更改为
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE
相结合,均无济于事。最后,我还尝试过用
fstream
std::fstream::in | std::fstrem::out
打开
std::fstream::binary
。没有骰子。所有这些尝试都产生了以下输出。

新输出

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

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

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

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

Read Named Pipe with _lread 中声称可以使用

_lopen
来完成我正在寻找的事情,但我无法控制如何进行与管道的事务,除此之外出于读取数据的目的,将被视为常规文件。

尽管有相反的说法(Fifo 文件 Windows 示例),但我找不到如何在 Windows 中执行

mkfifo
等效操作的真实示例(使用 istream 从命名管道读取)。

c++ winapi named-pipes
1个回答
0
投票

下面的服务器/客户端代码可以与我之前尝试过的代码进行对比,这是对我有用的解决方案,本着@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 . . .
© www.soinside.com 2019 - 2024. All rights reserved.