问题
由于一些不寻常的要求,我希望能够在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::out
和std::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
等价物的真实的例子。
1条答案
按热度按时间pu82cl6c1#
下面的服务器/客户端代码,可以与我之前尝试的代码进行对比,是对我有效的解决方案,本着@user207421的建议。
服务器端
字符串
Client
型
输出
先启动服务器,后启动客户端
服务端
型
Client
型
启动Client First,Server Next
Client
型
服务端
型