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

ssm49v7z  于 11个月前  发布在  Windows
关注(0)|答案(1)|浏览(121)

问题

由于一些不寻常的要求,我希望能够在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和“文件路径”的客户端版本的代码中,我获得了以下输出:

Server优先

先启动,直到客户端启动才输出:

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

下一个客户端

下一个开始,立即输出:

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


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

Client First

首先启动,立即输出等待消息,一旦服务器启动,则输出成功消息:

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来实现这一点,但我真的希望使用管道的名称,就好像它是一个常规的文件路径来读取数据一样。

What I Tried

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

尝试服务器代码

#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,都无济于事。最后,我也试过用std::fstream::in | std::fstrem::outstd::fstream::binary打开fstream。没有骰子。所有这些尝试都产生了下面的输出。

新增输出

在客户端,我得到了和以前一样的输出,而在服务器端,我尝试了所有的方法,

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


事实上,一个几乎完全对应于我想要的写数据的工作给了我希望,这可以用来读数据,但是,当然,我完全意识到这是不能保证的。

其他可能的解决方案(以及为什么它们不适合我)

Read Named Pipe with _lread中声称可以使用_lopen来完成我正在寻找的任务,但我无法控制如何使用管道进行事务,除了将其视为常规文件以阅读数据之外。
尽管有相反的说法(Fifo file Windows example),但我找不到如何在Windows(using istream to read from named pipe)中做mkfifo等价物的真实的例子。

pu82cl6c

pu82cl6c1#

下面的服务器/客户端代码,可以与我之前尝试的代码进行对比,是对我有效的解决方案,本着@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;
}

字符串

Client

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

Client

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

启动Client First,Server Next

Client

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

相关问题