【问题标题】:Basic Named Pipe to send data between c++ programs?基本命名管道在 C++ 程序之间发送数据?
【发布时间】:2018-02-16 09:00:35
【问题描述】:

我知道以前有人问过这个问题,但我找不到答案,所以我发帖寻求帮助。

我有一个 DLL,一旦注入到进程中,就会创建一个命名管道。管道将等待客户端连接,并将数据发送到客户端,直到客户端断开连接。

客户端,它只会连接到管道并接收数据并处理这些数据。

我的问题是,我希望能够发送超过 1 种类型的数据,例如浮点数、整数、字符串等。如何将数据重建为正确的数据(浮点数、整数字符串等)?

这是我的客户代码:

HANDLE hPipe;
DWORD dwWritten;
char Buffer[1024];

hPipe = CreateFile(TEXT("\\\\.\\pipe\\Pipe"),
    GENERIC_READ | GENERIC_WRITE,
    0,
    NULL,
    OPEN_EXISTING,
    0,
    NULL);
if (hPipe != INVALID_HANDLE_VALUE)
{
    WriteFile(hPipe,
        Buffer, //How do I put all the data into a buffer to send over to the client?
        sizeof(Buffer),   // = length of string + terminating '\0' !!!
        &dwWritten,
        NULL);


    CloseHandle(hPipe);
}

服务器:

wcout << "Creating Pipe..." << endl;

HANDLE hPipe;
char buffer[1024];
DWORD dwRead;


hPipe = CreateNamedPipe(TEXT("\\\\.\\pipe\\Pipe"),
    PIPE_ACCESS_DUPLEX,
    PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,   // FILE_FLAG_FIRST_PIPE_INSTANCE is not needed but forces CreateNamedPipe(..) to fail if the pipe already exists...
    1,
    1024 * 16,
    1024 * 16,
    NMPWAIT_USE_DEFAULT_WAIT,
    NULL);
while (hPipe != INVALID_HANDLE_VALUE)
{
    if (ConnectNamedPipe(hPipe, NULL) != FALSE)   // wait for someone to connect to the pipe
    {
        while (ReadFile(hPipe, buffer, sizeof(buffer) - 1, &dwRead, NULL) != FALSE)
        {
            /* add terminating zero */
            buffer[dwRead] = '\0';

            /* do something with data in buffer */
            printf("%s", buffer);
        }
    }

    DisconnectNamedPipe(hPipe);
}

我的问题是,我有一堆数据想一次性发送给客户端,其中可能包含 float、int、double 等内容。一旦我从服务器收集了所有数据,我会喜欢将它发送给客户端并让客户端通过像这样拆分数据来解析它:

void split(const string& s, char c,
    vector<string>& v) {
    string::size_type i = 0;
    string::size_type j = s.find(c);

    while (j != string::npos) {
        v.push_back(s.substr(i, j - i));
        i = ++j;
        j = s.find(c, j);

        if (j == string::npos)
            v.push_back(s.substr(i, s.length()));
    }
}

我对如何将所有数据发送到客户端并正确获取原始值有点迷茫?

【问题讨论】:

  • 您的代码不完整;特别是,它似乎缺少一个main() 函数和至少一个#include。请edit您的代码,这是您的问题的minimal reproducible example,然后我们可以尝试重现并解决它。您还应该阅读How to Ask
  • 我只发布了我正在使用的函数,如果需要,我可以添加 main() 函数和#include。

标签: c++ c++11 winapi c++14


【解决方案1】:

您必须使用一个库来将您的数据转换为可以通过套接字发送的东西。这称为序列化程序!您也可以使用序列化器来序列化为数据流,或者也可以在 GUI 或其他任何东西中。接收数据只需要“反序列化”。

您可以找到很多序列化程序库,例如:

http://www.boost.org/doc/libs/1_66_0/libs/serialization/doc/index.html

https://uscilab.github.io/cereal/

还有很多!

自定义代码看起来像(伪代码!):

class Check
{
    int i;
    float f;

    template<SERIALIZER_TYPE>
    void Serialize( SERIALIZER_TYPE& ser )
    {
         ser & i & f;
    }
 };

 int main()
 {
     Check c;
     std::string s;

     Socket socket( ip, port );

     WriteSerializer   ser(socket);
     ser & c & s;
 }   

如您所见,您无需为“了解”数据类型如何序列化而编写自己的内容。类/结构必须提供序列化器方法,以便它们也可以拆分为本地数据类型。

编辑: 补充来自 cmets 的问题:

Is it possible to send this data from my DLL to the EXE through named pipe instead of saving the file?

cereal 取自文档:

cereal 具有出色的标准库支持以及二进制、XML 和 JSON 序列化程序。如果您需要其他东西,谷物被编写为可轻松扩展以添加自定义序列化档案或类型。

因此,一种选择就是根据您的需要编写新界面。

但是看看示例代码:

void x()
{
    std::ofstream file( "out.xml" );
    cereal::XMLOutputArchive archive( file ); // depending on the archive type, data may be
                                        // output to the stream as it is serialized, or
                                        // only on destruction
    archive( some_data, more_data, data_galore );
}

如您所见,使用了std::ofstream 作为输出。因此,您可以简单地使用为套接字打开的 ofstream。

回答了如何将 std::ostream 与套接字连接,例如这里: How can I create an 'ostream' from a socket?

但这项工作也很简单,也是手工完成的。您只需为套接字编写自己的缓冲区类并将其连接到 ofstream。我相信不会超过 10 行代码!

因此,您现在可以通过套接字将变量和对象以 xml、json 或其他格式传输。

编辑:来自 cmets:是的,使用管道代替套接字也将适用于 iostream,并且该技术完全相同,并且基于实现 streambuf 周围的东西。 c++ connect output stream to input stream

我希望它在 Windows 上也能以同样的方式工作,并且 istream 也可以适应 ostream。

我相信,在这里以问答方式不再适合详细了解完整的解决方案。因此,如果还有关于将某些东西连接到 iostream 的问题,请提出新问题!

【讨论】:

  • 您好,感谢您的回复,从谷物快速入门页面,我看到它可以保存数据。是否可以通过命名管道将这些数据从我的 DLL 发送到 EXE,而不是保存文件? (它保存为 XML、JSON 或二进制文件。(uscilab.github.io/cereal/quickstart.html)
  • @TheNewbie:添加了更多如何写入不同文件的内容。
  • 这无法承认,Windows 中的管道就是管道。不是插座。虽然管道可以在消息模式下运行,但您仍然必须考虑部分消息。此外,您对 “序列化” 的解释是有限的,仅关注您提出的用例。序列化更广泛:它是翻译结构化信息的过程,以便可以将其作为流传输以供以后重构。
  • @IInspectable:you still have to account for partial messages。绝对地!一些序列化程序库意识到了这个问题并提供了特殊的“数据类型”来跟踪完整的数据包。如果没有,用户必须处理完整的包,并且必须等待完整性和/或拆分,如果可以接收多个(可能是最后一个部分)包。但是,如果您为此编写手工代码,情况也是如此。所以一定有一些我们有“原子”数据传输的东西。
  • 消息模式下的管道提供消息的原子传输。对于每次阅读,您至少会收到一条完整的信息。如果中间缓冲区太小,读取将返回适当的代码。这与套接字非常不同。因此,我的评论是,当 OP 询问管道时谈论套接字是没有帮助的。
【解决方案2】:

如果您使用WriteFile API 写入管道,则可以发送字节缓冲区。我喜欢@Klaus 关于序列化以打包所有数据的想法,如果我需要通过管道从客户端发送对象到服务器,那将是我的选择实现。

但是,如果您只需要发送一些数据对(例如“abc 1.12345”),我认为这有点过头了。我会简单地将它们放在带有已知分隔符的缓冲区中,然后从服务器发送到客户端,然后在客户端解析字符串。

回答您的问题“//如何将所有数据放入缓冲区以发送给客户端?”

这里有一些代码sn-ps:

std::string strToSend;
// ... some initialization
std::wstring wstr = std::wstring(strToSend.begin(), strToSend.end());
LPTSTR pchStr = wstr.c_str();
LPCTSTR pchSend;
StringCchCopy( pchSend, 1024, pchStr );

WriteFile(hPipe, pchSend, ... 调用中使用pchSend

还请查看以下示例以获取一些代码创意:Named Pipe Client

【讨论】:

  • 您好,感谢您的 sn-p。我尝试对其进行测试,但出现 2 个错误。 a value of type "const wchar_t *" cannot be used to initialize an entity of type "LPTSTR" 和:argument of type "LPCTSTR" is incompatible with parameter of type "STRSAFE_LPWSTR"
  • 我需要您的项目准确说明发生了什么。根据您需要#include &lt;strsafe.h&gt; 的错误,它是StringCchCopy 声明的位置(第二个错误)。 wchar_t* 实际上与 LPTSTR 相同。这取决于您使用的是什么环境。无论如何,您都必须使用 LPCTSTR 和 LPTSTR 来操作 API 调用,因此您可能需要自己进行字符串 -> LPTSTR 转换:检查此question
【解决方案3】:

首先我们需要了解这里的管道是什么。管道和队列是多个进程之间通信的媒介。您在队列或管道中提交/删除的每条消息作为一组消息都是唯一的,并且在您阅读它时,一次只会读取一条消息。如果您将1020.45“Hellow” 放入管道并从客户端读取它们,它将读取10 作为第一条消息20.45 作为第二条消息和“Hellow” 作为第三条消息。通常它们会出现一个字符串值,您需要将其转换为适当的类型。您应该有自己的逻辑来检查值是intfloat 还是普通字符串。首先只需读取数据并将它们全部打印出来,然后思考如何处理它们。

【讨论】:

  • 我打算将所有数据组合成一个字符串,并带有一个像“#”这样的拆分字符。示例:name#age#foo#bar -- 然后我将在 # 处拆分,然后选择我想要的值,例如 [1] 将是年龄,然后将其转换回 int
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-11
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-09
  • 1970-01-01
相关资源
最近更新 更多