Reputation: 339
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
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