我正在尝试扩展我的编程知识,并尝试进行一些多进程编程。
我想执行以下操作:在同一主机上运行多个可执行文件。其中一个可执行文件负责扫描文件系统,其中一个可执行文件正在处理数据等。
但是,有些数据必须在主机外传输。为了限制网络防火墙设置之类的东西,我希望有一个守护进程(多线程)通过IPC接收数据,然后使用尚未确定的套接字实现将其发送到外部主机。
经过大量的搜索和研究后,最明显的模式是消费者/生产者模式,多流程生产者(生成消息的守护进程)和多线程消费者(接收数据,最好通过共享内存) ,并将其发送给外部主机。)
我希望我的应用程序能够尽可能地跨平台运行。为此,我使用boost :: interprocess:message_queue。因为这个Boost库只接受二进制序列化对象,所以我使用Google Protobuf来处理序列化和反序列化。
我创建了2个可执行文件,目前称为“consumer”和“producer”。生产者通过消息队列将消息发送给消费者,消费者反过来反序列化消息。传递简单的“int”对象(在我看来,这意味着消息队列通信正在工作)时,下面的代码有效,但在使用SerializeToOstream()的数据时不起作用。
您可能已经注意到,我是IPC和多进程编程的新手,但我相信我已完成了我的作业。
这是我的producer.cpp:
#include <iostream>
#include <chrono>
#include <thread>
#include <fstream>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/ipc/message_queue.hpp>
#include <boost/thread.hpp>
#include <internal/messages/testmessage.pb.h>
int main(int argc, char** argv) {
// Construct the object to be passed
GOOGLE_PROTOBUF_VERIFY_VERSION;
struct protoremove {
~protoremove(){ google::protobuf::ShutdownProtobufLibrary(); }
} remover;
ib::protobuf::testMessage myMessage;
myMessage.set_id(10);
myMessage.set_version(1);
std::cout << myMessage.DebugString() << std::endl;
// Initialize the Boost message queue
try{
//Open a message queue.
boost::interprocess::message_queue mq
(boost::interprocess::open_or_create
,"message_queue" //name
,100 //max message number
,1000 //max message size
);
// Send our message
std::ofstream buftosend;
myMessage.SerializeToOstream(&buftosend);
mq.send(&buftosend, sizeof(buftosend), 1);
}
catch(boost::interprocess::interprocess_exception &ex){
std::cout << ex.what() << std::endl;
return 1;
}
return 0;
}
consumer.cpp:
#include <iostream>
#include <fstream>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/ipc/message_queue.hpp>
#include <boost/thread.hpp>
#include <internal/messages/testmessage.pb.h>
int main(int argc, char** argv) {
// Open the message queue
try {
//Erase previous message queue
boost::interprocess::message_queue::remove("message_queue");
ib::protobuf::testMessage recvdMessage;
//Create a message_queue.
boost::interprocess::message_queue mq
(boost::interprocess::open_or_create
,"message_queue" //name
,100 //max message number
,1000 //max message size
);
unsigned int priority;
boost::interprocess::message_queue::size_type recvd_size;
std::ifstream incomingbuf;
mq.receive(&incomingbuf, 1000, recvd_size, priority);
recvdMessage.ParseFromIstream(&incomingbuf);
recvdMessage.id();
recvdMessage.DebugString();
}
catch(boost::interprocess::interprocess_exception &ex){
boost::interprocess::message_queue::remove("message_queue");
std::cout << "IP error " << ex.what() << std::endl;
return 1;
}
boost::interprocess::message_queue::remove("message_queue");
return 0;
}
消息定义(.proto):
package ib.protobuf;
message testMessage {
required int32 version = 1;
optional int64 id = 2;
optional string data = 3;
optional int64 sequencenumber = 4;
}
运行使用者时,它会等待数据(mq.receive()调用正在阻塞)。 当生产者开始时,消费者获得SIGSEGV。 gdb在其回溯中指示这发生在第44行,即ParseFromIstream()方法。 Producer在DebugString()中输出正确的值。
(gdb) r
Starting program: /home/roel/bin/consumer
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
Program received signal SIGSEGV, Segmentation fault.
std::istream::sentry::sentry (this=0x7fffffffe117, __in=..., __noskip=true)
at /build/gcc-multilib/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/istream.tcc:50
50 /build/gcc-multilib/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/istream.tcc: No such file or directory.
(gdb) bt
#0 std::istream::sentry::sentry (this=0x7fffffffe117, __in=..., __noskip=true)
at /build/gcc-multilib/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/istream.tcc:50
#1 0x00007ffff679f7ab in std::istream::read (this=0x7fffffffe380,
__s=0x637d20 "", __n=8192)
at /build/gcc-multilib/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/istream.tcc:653
#2 0x00007ffff6b10030 in google::protobuf::io::IstreamInputStream::CopyingIstreamInputStream::Read(void*, int) () from /usr/lib/libprotobuf.so.9
#3 0x00007ffff6a99fe1 in google::protobuf::io::CopyingInputStreamAdaptor::Next(void const**, int*) () from /usr/lib/libprotobuf.so.9
#4 0x00007ffff6a97950 in google::protobuf::io::CodedInputStream::Refresh() ()
from /usr/lib/libprotobuf.so.9
#5 0x00007ffff6a94da3 in google::protobuf::MessageLite::ParseFromZeroCopyStream(google::protobuf::io::ZeroCopyInputStream*) () from /usr/lib/libprotobuf.so.9
#6 0x00007ffff6af5ad9 in google::protobuf::Message::ParseFromIstream(std::istream*) () from /usr/lib/libprotobuf.so.9
#7 0x0000000000407e35 in main (argc=1, argv=0x7fffffffe6d8)
at /home/roel/source/consumer.cpp:44
(gdb)
这是使用CMake和GCC 6.0.1在Linux上编译的。 我对我的计划有很多疑问:
Q1.
首先; 可能导致分段错误的原因是什么?
我究竟做错了什么?我已经看了好几个小时的代码,但看不出问题。
Q2.
在boost :: interprocess :: message_queue中
构造函数,我必须定义2个参数;最大数量
消息,以及大小。对于标准类型,此大小为
固定。但是,使用消息(通常),消息的大小
是可变的。那么,确定金额的最佳方法是什么
要为消息保留的内存?我应该设置一个最大值
每条消息的大小并创建一些多部分消息参数?
Q3.
有没有更好的方法来实现我的目标?序列化数据,
将它放入队列似乎是如此......复杂,尤其如此
看到这可能是一个非常普遍的问题。必须有更多
人们试图创建跨平台的IPC。像ZeroMQ这样的图书馆
仅支持UNIX域套接字。使用TCP套接字进行环回
界面看起来很难看。是不是只有一个图书馆让我
将任意对象(大小和布局)作为消息放在共享对象中
内存段,消费者可以pop()
?我的意思是,在一个
单线程,这可以通过堆栈上的push()
和pop()
来修复。
执行所有这些额外步骤似乎需要很多开销。
提前感谢您的回复。
As The Dark指出,上面的代码使用std::string
的实例而不是实际的字符串(std::string.data())
producer.cpp的回答如下:
std::string str = myMessage.SerializeAsString();
mq.send(str.data(), str.size(), 1);
但是,这对于consumer.cpp不起作用,因为字符串初始化为0。
以下是我用于consumer.cpp的代码:
unsigned int priority;
boost::interprocess::message_queue::size_type recvd_size;
//Reserve 1000 bytes of memory for our message
char incomingBuffer[1000];
mq.receive(&incomingBuffer, 1000, recvd_size, priority);
ib::protobuf::testMessage recvdMessage;
//Only if string object is really required
std::basic_string<char> str = incomingBuffer;
std::cout << "Message: " << str.data() << ". Size is " << recvd_size << std::endl;
//ParseFromString() can also directly parse "incomingBuffer", avoiding the cast above
recvdMessage.ParseFromString(str.data());
std::cout << "Message ID " << recvdMessage.id() << std::endl;
std::cout << recvdMessage.DebugString();
答案 0 :(得分:0)
这部分制片人似乎错了。
// Send our message
std::ofstream buftosend;
myMessage.SerializeToOstream(&buftosend);
mq.send(&buftosend, sizeof(buftosend), 1);
ofstream
尚未打开,因此没有存储任何内容的文件,因此第一次调用将失败(不会崩溃)。发送调用正在整个线路上发送原始ofstream
类结构。这不是可传输的格式。
我认为你想要的是序列化为ostringstream,然后传输ostringstream的内容(而不是整个对象)。
类似的东西:
// Send our message
std::ostringstream buftosend;
myMessage.SerializeToOstream(&buftosend);
std::string str = buftosend.str();
mq.send(str.data(), str.size(), 1);
或者更好:
// Send our message
std::string str = myMessage.SerializeAsString();
mq.send(str.data(), str.size(), 1);
您还可以添加一个调试行来显示str
的内容,但请注意它是二进制的,因此不易读。
您的消费者可能遇到类似问题(ifstream需要在文件上打开)。
答案 1 :(得分:0)
<强> A3:
强>
ZeroMQ
强> 就ZeroMQ
而言,有许多不同的传输类可以同时使用。因此,如果希望使用本地线程间信令的最低开销,那么我们使用 inproc://
传输类,如果进行本地进程间,可能{{1} } / .bind()
使用 .connect()
传输类。对于平台间分布式处理, ipc://
或 tcp://
或 pgm://
传输类制作在与系统和网络功能一致的通信需求方面的选择很容易。
epgm://
强> 另一个智能&amp;轻量级无代理消息传递/信令框架 nanomsg
来自nanomsg
的共同父亲Martin SUSTRIK。同样, ZeroMQ
, INPROC
和 IPC
,传输类已准备就绪。 Definitely worth a few minutes to read his insightfull remarks on this subject.
如所讨论的那样,使用广义共享内存设计进入其他架构,而TCP
ZeroMQ
和inproc://
{{ 1}} 传输类是零拷贝几乎 - 零延迟这个概念的例子。