1

大学のプロジェクトで、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 ラッパーに何らかの読み取りメソッドを用意することです。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;
}

私が得るのは、セグメンテーション違反または完全に破損したデータだけです (ただし、このデータには必要なものが含まれている場合があります)。

4

5 に答える 5

3

std::stringにメンバーを含む構造体へのポインターを渡すことはできませんmsgrcv。これは、インターフェースコントラクトに違反します。

に渡される2番目のパラメーターは、サイズがの3番目のパラメーターでmsgrcvある形式の「プレーン」C構造体を格納するのに十分なスペースを持つバッファーを指す必要があります。struct { long mtype; char mdata[size]; };msgrcv

残念ながら、このバッファのサイズの決定は、アライメントの問題の可能性があるために依存sizeする可能性がありますが、この種のインターフェイスを提供するシステムではないと想定する必要があります。標準offsetofマクロを使用して、このサイズを決定できます。

vectorストアはそのコンポーネントを連続して格納するため、バッファーのサイズがわかれば、のサイズを変更しvectorcharこれを使用してバッファーを保持できます。を使用すると、手動でバッファリング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);
}

逆にするにはstruct、msgsndインターフェイスで必要とされるように、データをハック互換のデータ配列にパックする必要があります。他の人がポインタを持っているので、それは良いインターフェースではありませんが、実装で定義された動作と配置の懸念について詳しく説明すると、このようなものが機能するはずです。

例えば

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();
    }
}
于 2009-07-25T11:28:24.310 に答える
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 文字が明示的に書き込まれない限り、バッファで受信したテキストは null で終了しないことに注意してください。そのような非ヌル終了テキストはstd::string、それから を構築するかstd::string( msg->mtext, len )、またはバッファに 0 用に 1 バイトを予約して、メッセージを受信した後に、msgrcv()

単純な char 配列の代わりに、アイテムが連続したメモリ範囲に格納されることを保証する std::vector を使用できます。次のコードは、メッセージ受信ループで使用できます。メッセージが収まらない場合、バッファ サイズが自動的に増加します。

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
    ...
}
于 2009-08-05T07:31:58.177 に答える
1

あなたは C と C++ を自由に混ぜているように見えるので、私も同じようにします。関数を完全に C で記述し、それを独自の翻訳単位に入れてから C++ から呼び出す必要があることに注意してください。ただし、これにより当面の問題は解決されます。あなたの混乱の根源は、間違いなく msgrcv の API の貧弱な設計にあるようです。msg 構造体のデータ メンバを、ポインタ、参照、またはメモリの生のチャンク以外のものにすることはできません。msgrcv は、データを構造体に直接配置します。

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


/* 呼び出し元は、データが少なくとも長さの文字を指していることを確認する必要があります */
bool read_msg( char *data, int length )
{
    ブールステータス = false;
    構造体メッセージ {
        長い mtype;
        文字データ[1];
    } *m = (struct msg *)malloc( length + offsetof( struct msg, data ));

    if( m != NULL ) {
        if( msgrcv( MSGQ_ID, m, 長さ, MSQ_ID, 0 ) == 長さ ) {
            memcpy( データ, m->データ, 長さ );
            ステータス = 真;
        }
    }

    無料 ( メートル );

    ステータスを返します。
}



于 2009-07-31T13:00:01.007 に答える
1

根本的な原因 セグメンテーション エラーの原因は、memcpy が構造体のサイズより大きいメッセージをコピーしようとしたことです。構造体には、長い (4 バイト) と std:string の余地しかありません。文字列は可変サイズにすることができますが、文字列として使用する場合のみです (これには自動的にメモリが割り当てられ、ポインターが維持されます)。std:string 変数は次のようになります

struct {
  unsigned int stringlength;
  char *pString;
}

コードは msg にコピーされます。この (簡略化された) 例では、長さはわずか 8 バイトです。そして、ポインターをデータで上書きします。これは、後でメモリの場所として解釈されます。

解決策 解決策は、ヘッダー (mtype) とsizeバイトのメッセージの両方を保持するのに十分な大きさのメモリ ブロックを割り当てることです。これは一時的なスペースにすぎないため、これにはスタック変数を使用することをお勧めします。そう

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

サイズがグローバル定数である場合は、サイズをより一意で意味のあるものに置き換えることをお勧めします。サイズはバイト単位で表されることに注意してください。size文字の std::string を送信する必要がある場合は、配列の長さを少なくとも sizeof(std::string)+1 (構造のオーバーヘッドと \0 文字のため) に増やす必要があります。もう少し良い。

于 2009-08-04T07:30:08.180 に答える
1

これは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(サイズ * sizeof(char));

msgrcv(MSGQ_ID, msg.data, サイズ, MSQ_ID, 0);

mtype はスタックに割り当てられ、データはヒープに割り当てられます。msgreceive が指定されたポインタに対して一種の memcpy を作成すると、いくつかの問題が発生します。

于 2009-07-25T10:56:26.590 に答える