如何从消息队列中接收动态长度数据?

时间:2009-07-25 10:25:45

标签: c++ malloc message-queue

我必须使用SysV消息队列为大学项目发送和接收动态数据。

数据的长度在单独的消息中传输,size因此已知。

这就是我尝试接收数据的方式。我不得不承认我不是C ++专家,特别是在内存分配方面。

<击>

<击>
struct {
    long mtype;
    char *mdata;
} msg;

msg.mdata = (char *)malloc(size * sizeof(char));

msgrcv(MSGQ_ID, &msg, size, MSG_ID, 0);

问题似乎是malloc电话,但我不知道该怎么做。

修改

我尝试的是在消息队列周围的OO包装器中使用某种 read 方法。我想将消息队列中的数据读入char[]std::string。我现在看起来(简化)就像这样。

bool Wrapper::read(char *data, int length)
{
    struct Message {
        long mtype;
        std::string mdata;
    };

    Message msg;
    msg.mdata = std::string(size, '\0');

    if(msgrcv(MSGQ_ID, &msg, size, MSG_ID, 0) < 0)
    {
        return false;
    }

    memcpy(data, msg.mdata.c_str(), msg.mdata.size());

    return true;
}

我得到的只是分段错误或完全损坏的数据(尽管这些数据有时包含我想要的内容)。

5 个答案:

答案 0 :(得分:3)

您无法将包含std::string成员的结构的指针传递给msgrcv,这违反了接口合同。

传递给msgrcv的第二个参数需要指向一个缓冲区,该缓冲区有足够的空间来存储struct { long mtype; char mdata[size]; };形式的“普通”C结构,其中size是msgrcv的第三个参数

不幸的是,由于可能的对齐问题,确定此缓冲区的大小可能取决于size,但您必须假设它不在提供此类接口的系统上。您可以使用标准offsetof宏来帮助确定此大小。

vector连续存储其组件时,一旦知道缓冲区的大小,就可以调整vector char的大小并使用它来保存缓冲区。使用vector可以免除您freedelete[]手动缓冲的义务。

你需要做这样的事情。

std::string RecvMessage()
{
    extern size_t size; // maximum size, should be a parameter??
    extern int MSGQ_ID; // message queue id, should be a parameter??
    extern long MSG_ID; // message type, should be a parameter??

    // ugly struct hack required by msgrcv
    struct RawMessage {
        long mtype;
        char mdata[1];
    };

    size_t data_offset = offsetof(RawMessage, mdata);

    // Allocate a buffer of the correct size for message
    std::vector<char> msgbuf(size + data_offset);

    ssize_t bytes_read;

    // Read raw message
    if((bytes_read = msgrcv(MSGQ_ID, &msgbuf[0], size, MSG_ID, 0)) < 0)
    {
        throw MsgRecvFailedException();
    }

    // a string encapsulates the data and the size, why not just return one
    return std::string(msgbuf.begin() + data_offset, msgbuf.begin() + data_offset + bytes_read);
}

另一方面,您只需将数据打包到msgsnd接口所需的struct hack兼容数据阵列中。正如其他人指出的那样,它不是一个好的界面,而是掩盖了实现定义的行为和对齐问题,这样的事情应该有效。

e.g。

void SendMessage(const std::string& data)
{
    extern int MSGQ_ID; // message queue id, should be a parameter??
    extern long MSG_ID; // message type, should be a parameter??

    // ugly struct hack required by msgsnd
    struct RawMessage {
        long mtype;
        char mdata[1];
    };

    size_t data_offset = offsetof(RawMessage, mdata);

    // Allocate a buffer of the required size for message
    std::vector<char> msgbuf(data.size() + data_offset);

    long mtype = MSG_ID;
    const char* mtypeptr = reinterpret_cast<char*>(&mtype);

    std::copy(mtypeptr, mtypeptr + sizeof mtype, &msgbuf[0]);
    std::copy(data.begin(), data.end(), &msgbuf[data_offset]);

    int result = msgsnd(MSGQ_ID, &msgbuf[0], msgbuf.size(), 0);
    if (result != 0)
    {
        throw MsgSendFailedException();
    }
}

答案 1 :(得分:1)

这是an example for SyS。我希望它会有所帮助。

使用malloc的方式似乎是正确的,但在为IPC进行内存分配时应该非常小心。您应该检查其他进程如何管理内存(字节对齐,大小,平台......)

在你的代码中,mtype的目的是什么?您收到的尺寸是否考虑了这个mtype?或者它只是mdata的大小?

更新:mtype是消息的一部分吗?

如果是这样的话:

msgsize = size * sizeof(char)+ sizeof(long)

pmsg = malloc(msgsize);

msgrcv(MSGQ_ID,pmsg,msgsize,MSQ_ID,0);

如果不是

msg.data =(char *)malloc(size * sizeof(char));

msgrcv(MSGQ_ID,msg.data,size,MSQ_ID,0);

当数据在堆上分配时,mtype在堆栈上被分配。如果msgreceive在给定指针上产生一种memcpy,则会引起一些麻烦。

答案 2 :(得分:1)

你似乎在自由地混合C和C ++,所以我也会这样做。 请注意,您可能应该完全用C编写函数,将其放在自己的转换单元中,然后从C ++调用它,但这应该可以解决您的问题。 似乎你的混乱的根源可以说是msgrcv的API设计不佳。你不能让msg结构的数据成员成为指针或引用或除了原始内存块以外的任何东西 - msgrcv将把数据直接放入结构中。

#include <stddef.h>
#include <stdlib.h>
#include <string.h>


/* Caller must ensure that data points to at least length chars */
bool read_msg( char *data, int length )
{
    bool status = false;
    struct msg {
        long mtype;
        char data[ 1 ];
    } *m = (struct msg *)malloc( length + offsetof( struct msg, data ));

    if( m != NULL ) {
        if( msgrcv( MSGQ_ID, m, length, MSQ_ID, 0 ) == length ) {
            memcpy( data, m->data, length );
            status = true;
        }
    }

    free( m );

    return status;
}



答案 3 :(得分:1)

根本原因 您的segementation错误的原因是memcpy尝试复制大于结构大小的消息。你的struct只有很长的空间(4个字节)和std:string。字符串可以是可变大小,但仅在用作字符串时(它将自动为此分配内存并维护指针)。 std:string变量看起来像

struct {
  unsigned int stringlength;
  char *pString;
}

您的代码复制到msg中,在此(简化)示例中只有8个字节长。然后用数据覆盖指针,数据稍后被解释为内存位置。

<强>解决方案 一种解决方案是分配足够大的内存块来保存标头(mtype)和 size 字节的消息。由于它只是临时空间,我建议使用堆栈变量。所以

 struct msg {
        long mtype;
        char data[ size ];
    } 

如果它是一个全局常量,我建议用更独特和有意义的东西替换 size 。请注意, size 以字节表示;如果你需要发送一个 size 字符的std :: string,你需要增加数组长度至少sizeof(std :: string)+1(对于结构开销和\ 0字符);更好一点。

答案 4 :(得分:1)

分段错误或数据损坏的原因是您正在为msgrcv()msgsnd()函数使用不正确的消息结构。这些函数需要使用以下结构:

struct msgbuf {
    long mtype;     /* message type, must be > 0 */
    char mtext[1];  /* message data */
};

但是你的Message结构包含一个std::string,它在堆上分配内存,但msgbuf结构需要一个从mtext[0]开始的内存区域。 mtext成员不是指针,而是结构中的char数组。由于大小未知,数组被声明为1大小的数组,但实际上调用者应提供更大的缓冲区。此技术也用于某些Windows API。

为了能够接收消息,您需要一个缓冲区

static const unsigned int BufferSize = 256;
char* buffer = new char[ sizeof(long) + BufferSize ];
msgbuf* msg = reinterpret_cast< msgbuf* >( buffer );

然后可以将此msg传递给msgrcv()。并且不要忘记delete[]缓冲区。

另请注意,缓冲区中接收的文本不会以空值终止,除非在发送消息时显式写入0字符。这样的非空终止文本可用于从中构造std::string - 例如std::string( msg->mtext, len )或在缓冲区中为0保留一个字节,并在收到msgrcv()

返回的长度的消息后写入

您可以使用std :: vector来保证项目存储在连续的内存范围内,而不是普通的char数组。可以在消息接收循环中使用以下代码,如果消息不适合,则会自动增加缓冲区大小。

static const unsigned int InitialBufferSize = 32;
std::vector<char> buffer( InitialBufferSize );
msgbuf* msg = new (&buffer[0]) msgbuf();
// reserve space in the vector for the mtype member of msgbuf structure
std::vector<char>::size_type available = buffer.size() - sizeof(long);

for(;;)
{
    const ssize_t count = ::msgrcv( MSGQ_ID, msg, available, 0, 0 );
    if( -1 == count )
    {
        if( E2BIG == errno )
        {
            buffer.resize( buffer.size() * 2 );
            msg = new (&buffer[0]) msgbuf();
            available = buffer.size() - sizeof(long);
            continue;
        }
        perror( "Failed to read message from queue" );
        break;
    }

    // handle the received message
    ...
}