farzane
farzane

Reputation: 339

Message queue in Perl

I want to implement a message queue with Perl. I get data from stdin and send it to the queue.

My message's structure is

struct message => {
    mtype        => '$',
    buffer_size  => '$',
    last_message => '$',
    buff         => '$',
};

I have to receive data from the queue with a C program. My C program works well before, but now when I run it to receive data from queue it shows me something like this

age=HASH(0x1daa088) 1936942445      4000        

I read chunks of data with a 4000-byte buffer that I print on stdout. But instead of age=HASH(0x1daa088) 1936942445 the program should print the size of message received.

What happened here? Is it because the message in C is a structure and in Perl it's a hash?

My C code:

#include <stdio.h>
#include <stdlib.h>
#include <linux/ipc.h>
#include <linux/msg.h>
#include <time.h>
#include <string.h>

#define bufsize 4000

struct mymsgbuf {
    long    mtype;          /* Message type */
    int     buffer_size;
    char    buff[bufsize];
    int     last_message;
} msg;

int read_message( int qid, long type, struct mymsgbuf *qbuf ) {

    int result, length;

    /* The length is essentially the size of the structure minus   sizeof(mtype)*/

    length = sizeof(struct mymsgbuf) - sizeof(long);

    if ( (result = msgrcv( qid, qbuf, length, type, MSG_NOERROR)) == -1 ) {
        return(-1);
    }

    fprintf(stderr, "\t%d\t\t%d\t\t%d  \n", qbuf->buffer_size, bufsize, qbuf->last_message);

    write(1,qbuf->buff,qbuf->buffer_size);

    return(result);
}

int open_queue( key_t keyval ) {

    int qid;

    if ( (qid = msgget( keyval, 0660 )) == -1 ) {
        return(-1);
    }

    return(qid);
}

main() {

    int     qid;
    key_t   msgkey;
    msg.last_message = 0;

    /* Generate our IPC key value */
    msgkey = ftok("/home/joobeen/Desktop/learning", 'm');

    /* Open/create the queue */
    if (( qid = open_queue( msgkey)) == -1) {
        perror("open_queue");
        exit(1);
    }

    fprintf(stderr, "byte received:\tbuffer_size:\tlast_message:\n");

    /* Bombs away! */
    while (1) {

        if ( (read_message( qid,0, &msg )) == -1 ) {
            perror("receive_message");
            exit(1);
        }
        if ( msg.last_message == 1 )
            break;
    }

    return 0;
}

My Perl code:

use strict;
use warnings;

use IPC::SysV qw(IPC_PRIVATE IPC_CREAT S_IRUSR S_IWUSR ftok);
use IPC::Msg;
use Class::Struct;

struct message => {
    mtype        => '$',
    buffer_size  => '$',
    last_message => '$',
    buff         => '$',
};

my $key_in = ftok( "/home/joobeen/Desktop/learning", 'm' );
my ( $buffer ) = "";
my $buf_size   = 4000;
my $file       = shift @ARGV;
my $ifh;
my $is_stdin  = 0;
my $type_sent = 1;
my $last;

if ( defined $file ) {
    open $ifh, "<", $file or die $!;
}
else {
    $ifh = *STDIN;
    $is_stdin++;
}

my $ipc_id = msgget( $key_in, IPC_CREAT | S_IRUSR | S_IWUSR );
my $msg = message->new(
    mtype        => 1,
    last_message => 0
);

print "\tbyte sent\tbuffer_size\tlast_message\n";

while ( <$ifh> ) {

    $last = read( $ifh, $buffer, $buf_size );

    $msg->buff( $buffer );
    $msg->buffer_size( $buf_size );

    if ( $last < $buf_size ) {
        $msg->last_message( 1 );
    }

    msgsnd( $ipc_id, pack( "l! a*", $type_sent, $msg ), 0 );

    print "\t", $last, "\t\t", $buf_size, "\t\t", $msg->last_message, "\n";

}

close $ifh unless $is_stdin;

Upvotes: 3

Views: 1526

Answers (1)

nwellnhof
nwellnhof

Reputation: 33638

Your code has multiple problems. I won't fix them all for you but I can give you some guideline on how to implement IPC in general.

Reading binary data directly into C structs is extremely fragile. You have to care about byte order, struct padding and the size of types like int or long. Depending on your platform, both of these types could be 32-bit or 64-bit and little or big endian. So first of all, you need an exact specification of the "on-the-wire protocol" of your messages. To simplify things, let's use fixed-sized messages:

mtype: 32-bit unsigned integer, little endian
buffer_size: 32-bit unsigned integer, little endian
buffer: 4000 bytes
last_message: 32-bit unsigned integer, little endian

This is just an example. You could use big endian integers as well, like most network protocols do for historical reasons. If you only want to use IPC on a single machine, you could also specify native byte order.

Now the length of a message is fixed to 4012 bytes. To decode such a message in a portable way in C, you should read it into a char array and extract each field separately. You know each field's offset and size.

Encoding such a message in Perl is easy using the pack function:

my $msg = pack('V V a4000 V', $mtype, $buffer_size, $buffer, $last);

There's no need for Class::Struct. This module does not do what you expect.

Upvotes: 5

Related Questions