【问题标题】:Windows: Check existence of named pipeWindows:检查命名管道是否存在
【发布时间】:2021-06-09 15:15:12
【问题描述】:

我正在编写一个在 Windows 上使用命名管道的 C++ 程序。我可以很好地创建和使用它们。唯一缺少的部分是检查管道是否存在的函数。

来自 Unix 世界,我最初尝试过 std::filesystem::exists("\\\\.\\pipe\\myPipe"),但这并不可靠,并且经常与 ERROR_PIPE_BUSY 出错。

在寻找检查管道是否存在的替代方法时,我偶然发现了 GitHub 上的this issue(Boost 进程),从那里我认为 Boos 进程通过使用特殊的命名方案和计数器来规避问题,并且然后在内部跟踪它(但似乎只适用于通过 Boost 进程创建的管道)。

此外,根据How can I get a list of all open named pipes in Windows?,似乎有办法列出现有的命名管道。这些解决方案虽然没有使用 C++,但我没有找到将其移植过来的方法。

在阅读了documentation of CreateNamedPipe 之后,我现在组装了以下解决方案来解决我的问题:

bool NamedPipe::exists(const std::filesystem::path &pipePath) {
    if (pipePath.parent_path() != "\\\\.\\pipe") {
        // This can't be a pipe, so it also can't exist
        return false;
    }

    // Attempt to create a pipe with FILE_FLAG_FIRST_INSTANCE so that the creation will fail
    // if the pipe already exists
    HANDLE pipeHandle = CreateNamedPipe(pipePath.string().c_str(),
                                        PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE,
                                        PIPE_TYPE_BYTE | PIPE_WAIT,
                                        1,   // # of allowed pipe instances
                                        0,   // Size of outbound buffer
                                        0,   // Size of inbound buffer
                                        0,   // Use default wait time
                                        NULL // Use default security attributes
    );

    if (pipeHandle == INVALID_HANDLE_VALUE) {
        // Creation has failed
        // It has failed (most likely) due to there alredy existing a pipe with
        // that name
        return true;
    } else {
        // The creation has succeeded
        if(!CloseHandle(pipeHandle)) {
            throw PipeException< DWORD >(GetLastError(), "CheckExistance");
        }

        return false;
    }
}

然而,试图创建一个命名管道只是为了检查是否已经存在一个具有该名称的管道似乎已经有很多不必要的开销。此外,我不确定此解决方案是否普遍适用,或者仅在测试的管道也是使用 FILE_FLAG_FIRST_PIPE_INSTANCE 创建时才有效。

因此我的问题是:有没有更好的方法来检查 Windows 中是否已经存在具有给定名称的命名管道?

【问题讨论】:

  • 我没有理由想要拥有该功能。它提供的唯一东西是 TOCTTOU 比赛。强大的软件不需要。
  • @IInspectable 我想在我的单元测试中使用它来检查管道是否已创建,然后按照我的预期再次删除。我不打算在生产中使用它。

标签: c++ windows named-pipes


【解决方案1】:

std::filesystem::exists("\\\\.\\pipe\\myPipe") 返回ERROR_PIPE_BUSY 表示它正在使用CreateFile() 实际连接到管道。 exists() 实现尝试打开请求的文件以检查其存在性并非没有道理。

根据CreateFile() documentation

如果至少有一个活动管道实例但服务器上没有可用的侦听器管道,这意味着所有管道实例当前都已连接,CreateFile 失败并显示ERROR_PIPE_BUSY

这意味着管道在技术上确实存在,但此时它还没有准备好接收新客户端。

the link you provided 中,提供的许多解决方案建议使用.NET 的System.IO.Directory.GetFiles() 方法来迭代"\\.\pipe\" 的内容。 This answer 显示该调用如何使用 FindFirstFile()FindNextFile() 转换为 Win32 API 调用。您可以轻松地在 C++ 中执行相同的 API 调用,例如:

bool NamedPipe::exists(const std::filesystem::path &pipePath)
{
    std::string pipeName = pipePath.string();
    if ((pipeName.size() < 10) ||
        (pipeName.compare(0, 9, "\\\\.\\pipe\\") != 0) ||
        (pipeName.find('\\', 9) != std::string::npos))
    {
        // This can't be a pipe, so it also can't exist
        return false;
    }
    pipeName.erase(0, 9);

    WIN32_FIND_DATA fd;
    DWORD dwErrCode;

    HANDLE hFind = FindFirstFileA("\\\\.\\pipe\\*", &fd);
    if (hFind == INVALID_HANDLE_VALUE)
    {
        dwErrCode = GetLastError();
    }
    else
    {
        do
        {
            if (pipeName == fd.cFileName)
            {
                FindClose(hFind);
                return true;
            }
        }
        while (FindNextFileA(hFind, &fd));

        dwErrCode = GetLastError();
        FindClose(hFind);
    }

    if ((dwErrCode != ERROR_FILE_NOT_FOUND) &&
        (dwErrCode != ERROR_NO_MORE_FILES))
    {
        throw PipeException< DWORD >(dwErrCode, "CheckExistance");
    }

    return false;
}

更新:或者,使用带有 Unicode API 的 std::wstring,因为文件系统在 Windows 上是原生 Unicode:

bool NamedPipe::exists(const std::filesystem::path &pipePath)
{
    std::wstring pipeName = pipePath;
    if ((pipeName.size() < 10) ||
        (pipeName.compare(0, 9, L"\\\\.\\pipe\\") != 0) ||
        (pipeName.find(L'\\', 9) != std::string::npos))
    {
        // This can't be a pipe, so it also can't exist
        return false;
    }
    pipeName.erase(0, 9);

    WIN32_FIND_DATAW fd;
    DWORD dwErrCode;

    HANDLE hFind = FindFirstFileW(L"\\\\.\\pipe\\*", &fd);
    if (hFind == INVALID_HANDLE_VALUE)
    {
        dwErrCode = GetLastError();
    }
    else
    {
        do
        {
            if (pipeName == fd.cFileName)
            {
                FindClose(hFind);
                return true;
            }
        }
        while (FindNextFileW(hFind, &fd));

        dwErrCode = GetLastError();
        FindClose(hFind);
    }

    if ((dwErrCode != ERROR_FILE_NOT_FOUND) &&
        (dwErrCode != ERROR_NO_MORE_FILES))
    {
        throw PipeException< DWORD >(dwErrCode, "CheckExistance");
    }

    return false;
}

【讨论】:

  • 请注意,此处列出的此代码无法编译。变量名中有一些拼写错误,必须通过显式调用path::string() 将路径转换为字符串。出于某种原因,我无法建议自己进行编辑以解决此问题。
  • @Raven 我只看到 1 个变量错字,我已经更正了。 std::filesystem::path 有一个 implicit conversion operatorstd::basic_string&lt;path::value_type&gt;,但我想在 Windows 上解析为 std::wstring,所以我添加了对 string() 的显式调用。不过,可能应该使用std::wstring 而不是std::string。我为此添加了第二个示例。
  • 您将hFindhFile 用于同一个变量。如果hFile 变量被重命名为hFind,那么一切都像魅力一样。感谢您的更新:+1:
猜你喜欢
  • 1970-01-01
  • 2016-09-23
  • 2016-06-29
  • 1970-01-01
  • 2012-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多