我正在编写一个在 Windows 上使用命名管道的 C++ 程序。我可以很好地创造并与他们一起工作。这个难题中唯一缺少的部分是检查管道是否存在的函数。
来自 Unix 世界,我最初尝试了 std::filesystem::exists("\\\\.\\pipe\\myPipe")
但这并不可靠,并且经常出错 ERROR_PIPE_BUSY
。
在寻找检查管道是否存在的替代方法时,我偶然发现了 this issue在 GitHub(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 中是否已存在具有给定名称的命名管道?
最佳答案
std::filesystem::exists("\\\\.\\pipe\\myPipe")
返回 ERROR_PIPE_BUSY
表示它正在使用 CreateFile( )
实际连接到管道。 exists()
实现尝试打开所请求的文件以检查其存在性并非不合理。
根据 CreateFile()
documentation :
If there is at least one active pipe instance but there are no available listener pipes on the server, which means all pipe instances are currently connected,
CreateFile
fails withERROR_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;
}
关于c++ - Windows:检查命名管道是否存在,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66585746/