Protobuf导致ParseFromIstream上的分段错误

时间:2016-06-12 23:32:28

标签: c++ serialization protocol-buffers

我正在尝试扩展我的编程知识,并尝试进行一些多进程编程。

我想执行以下操作:在同一主机上运行多个可执行文件。其中一个可执行文件负责扫描文件系统,其中一个可执行文件正在处理数据等。

但是,有些数据必须在主机外传输。为了限制网络防火墙设置之类的东西,我希望有一个守护进程(多线程)通过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();

2 个答案:

答案 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:// 传输类制作在与系统和网络功能一致的通信需求方面的选择很容易。

Do not hesitate to check other posts, with also a direct URL to Pieter HINTJENS' book, a must-read for going into distributed systems design

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}} 传输类是零拷贝几乎 - 零延迟这个概念的例子。