问题描述
我正在编写一个在 Windows 上使用命名管道的 C++ 程序。我可以很好地创造和使用它们。唯一缺少的部分是检查管道是否存在的函数。
来自 Unix 世界,我最初尝试过 std::filesystem::exists("\\\\.\\pipe\\myPipe")
,但这并不可靠,而且 ERROR_PIPE_BUSY
经常出错。
在寻找检查管道是否存在的替代方法时,我在 GitHub(Boost 进程)上偶然发现了 this issue,从那里我认为 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
会失败并返回 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;
}
UPDATE:或者,将 std::wstring
与 Unicode API 一起使用,因为文件系统在 Windows 上是原生的 Unicode:
bool NamedPipe::exists(const std::filesystem::path &pipePath)
{
std::wstring pipeName = pipePath;
if ((pipeName.size() < 10) ||
(pipeName.compare(0,L"\\\\.\\pipe\\") != 0) ||
(pipeName.find(L'\\',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,"CheckExistance");
}
return false;
}