现在的位置: 首页 > 综合 > 正文

消息队列(system V IPC)

2013年12月26日 ⁄ 综合 ⁄ 共 7441字 ⁄ 字号 评论关闭

消 息 队 列

消息队列是一种以链表式结构组织的一组数据,存放在内核中,是由各进程通过消息队列标识符来引用的一种数据传送方式。像其他两种IPC对象一样,也是由内核来维护。消息队列是三个IPC对象类型中最具有数据操作性的数据传送方式,在消息队列中可以随意根据特定的数据类型值来检索消息。

一、消息队列的概念

消息队列是一个消息的链接表,该表由内核进行维护及存储。消息队列相比其他的通信方式对数据进行更细致的组织。可以通过一个消息的类型来索引指定的数据,它在数据流的概念上扩展了数据传送的概念,可以根据需要只读取指定数据,该点是管道和FIFO所不能比拟的。本节中使用术语队列和队列ID
分别指消息队列和消息队列描述符。消息队列在多进程间通信示意如图14-10所示。

 

图14-10  消息队列示意图

对于每个队列都有一个msqid_ds结构体来描述队列当前的状态。该结构体定义如下:

struct msqid_ds{

    struct ipc_perm     msg_perm;  

    msgqnum_t   msg_qnum;      

    msglen_t    msg_qbytes;    

    pid_t       msg_lspid;     

    pid_t       msg_lrpid;         

    time_t      msg_stime;     

    time_t      msg_rtime;     

    time_t      msg_ctime;     

    ...

   

    ...

};

%说明:在不同的系统中,此结构会有不同的新成员,这里只列出最少拥有的关键成员。其中,msg_qbytes成员以及msg_qnum成员在不同的系统也会有不同的上限值,这里就不逐一介绍了,详细内容请参阅相关系统手册。

二、创建消息队列

消息队列是三个IPC对象类型中最具有数据操作性的数据传送方式,在消息队列中可以随意根据特定的数据类型值来检索消息。当然,其缺点也显而易见,为了维护该数据链表,就需要更多的内存资源,而且在数据读写上比起共享内存也更复杂一些,时间开销也更大一些。

函数msgget可以创建或打开一个队列,函数原型如下:

#include <sys/msg.h>

int msgget(key_t key, int flags);

函数中参数key用来转换成一个标识符,每一个IPC对象与一个key相对应。参数flags标明函数的行为。下面实例演示了使用msgget函数创建一个队列,函数中参数falgs指定为IPC_CREAT|0666,说明新建一个权限为0666的消息队列,其中组用户、当前用户以及其他用户拥有读写的权限。并在程序的最后使用shell命令ipcs
–q来查看系统IPC的状态。

(1)在vi编辑器中编辑该程序如下:

程序清单14-12  create_msg.c msgget函数

#include <sys/msg.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <stdio.h>

#include <stdlib.h>

int main ( void )

{

    int     qid;

    key_t       key;

   

    key = 113;

    qid=msgget( key, IPC_CREAT | 0666 );       /*创建一个消息队列*/

    if ( qid < 0 ) {                            /* 创建一个消息队列失败
*/

        perror ( "msgget" );

        exit (1) ;

    }

   

    printf ("created queue id : %d /n", qid );  /* 输出消息队列的ID
*/

   

    system( "ipcs -q" );                        /*查看系统IPC的状态*/

    exit ( 0 );

}

(2)在shell中编译该程序如下:

$gcc create_msg.c–o create_msg

(3)在shell中运行该程序如下:

$./ create_msg

created queue id : 0

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages

0x0000af40 623430   root       666        0            0

0x0000007b 0          root       666        0            0

在程序中使用了系统命令ipcs,命令参数-q说明只查看消息队列的状态。注意在输出消息中,key段标明的是IPC的key值,msqid为该队列的ID值,perms为执行权限。同样,队列的执行权限像其他IPC对象一样没有执行权限。函数msgctl可以在队列上做多种操作,函数原型如下:

#include <sys/msg.h>

int msgctl( int msqid, int cmd , struct msqid_ds
*buf );

参数msqid为指定的要操作的队列,cmd参数指定所要进行的操作,其中有些操作需要buf参数。cmd参数的详细取值及操作如表14-9所示。

表14-9  cmd参数详解

cmd

操    作

IPC_STAT

取队列的msqid_ds结构,将它存放在buf所指向的结构中(需要buf参数)

IPC_SET

使用buf所指向结构中的值对当前队列的相关结构成员赋值,其中包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_perm.cuid。该命令只能由具有以下条件的进程执行:进程有效用户ID等于msg_perm.cuid或msg_perm.uid超级用户进程。其中只有超级用户才可以增加队列的msg_qbytes的值

IPC_RMID

删除队列,并清除队列中的所有消息。此操作会影响后续进程对这个队列的相关操作。该命令只能由具有以下条件的进程执行。进程有效用户ID等于msg_perm.cuid或msg_perm.uid,超级用户进程

下面实例演示了调用msgctl函数操作队列,程序中先读取命令行参数,如没有,则打印命令提示信息,在调用msgctl函数执行删除操作的前后分别调用了一次shell命令ipcs –q来查看系统IPC的状态。

(1)在vi编辑器中编辑该程序如下:

程序清单14-13  del_msg.c 调用msgctl删除指定队列

#include <sys/msg.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <stdio.h>

#include <stdlib.h>

int main ( int argc ,char
*argv[] )

{

    int qid ;

   

    if ( argc != 2 ){ /* 命令行参数出错
*/

        puts ( "USAGE: del_msgq.c <queue ID >" );

        exit ( 1 );

    }

   

    qid = atoi ( argv[1] ); /* 通过命令行参数得到组ID
*/

    system( "ipcs -q");

   

    if ( ( msgctl( qid, IPC_RMID, NULL ) ) < 0 ){ /* 删除指定的消息队列
*/

        perror ("msgctl");

        exit (1 );

    }

    system( "ipcs -q");

    printf ( "successfully removed %d  queue/n", qid ); /* 删除队列成功
*/

    exit( 0 );

}

(2)在shell中编译该程序如下:

$gcc del_msg.c–o del_msg

(3)在shell中运行该程序如下:

$./ del_msg 0

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages   

0x0000007b 0          root       666        0            0          

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages   

successfully removed 0  queue  

三、读写消息队列

由于消息队列的特殊性,系统为这个数据类型提供了两个接口(msgsnd函数,msgrcv函数),分别对应写消息队列及读消息队列。将一个新的消息写入队列,使用函数msgsnd,函数原型如下:

#include <sys/msg.h>

int msgsnd ( int msqid, const void
*prt, size_t nbytes, int flags);

对于写入队列的每一个消息,都含有三个值,正长整型的类型字段、数据长度字段和实际数据字节。新的消息总是放在队列的尾部,函数中参数msqid指定要操作的队列,ptr指针指向一个msgbuf的结构,定义如下:

struct msgbuf{

    long mtype;

    char mbuf[];

};

这是一个模板的消息结构,其中成员 mbuf是一个字符数组,长度是根据具体的消息来决定的,切忌消息不能以NULL结尾。成员mtype是消息的类型字段。

函数参数nbytes指定了消息的长度,参数flags指明函数的行为。函数成功返回0,失败返回–1并设置错误变量errno。errno可能出现的值有:EAGAIN、EACCES、EFAULT、EIDRM、EINTR、EINVAL和ENOMEM。当函数成功返回后会更新相应队列的msqid_ds结构。

使用函数msgrcv可以从队列中读取消息,函数原型如下:

#include <sys/msg.h>

ssize_t msgrcv ( int msqid, void
*ptr, size_t nbytes, long type , int flag);

函数中参数msqid为指定要读的队列,参数ptr为要接收数据的缓冲区,nbytes为要接收数据的长度,当队列中满足条件的消息长度大于nbytes的值时,则会参照行为参数flag的值决定如何操作:当flag中设置了MSG_NOERROR位时,则将消息截短到nbytes指定的长度后返回。如没有MSG_NOERROR位,则函数出错返回,并设置错误变量errno。设置type参数指定msgrcv函数所要读取的消息,tyre的取值及相应操作如表14-10所示。

表14-10  type值详解

type

操    作

等于0

返回队列最上面的消息(根据先进先出规则)

大于0

返回消息类型与type相等的第1条消息

小于0

返回消息类型小于等于type绝对值的最小值的第1条消息

参数flag定义函数的行为,如设置了IPC_NOWAIT位,则当队列中无符合条件的消息时,函数出错返回,errno的值为ENOMSG。如没有设置IPC_NOWAIT位,则进程阻塞直到出现满足条件的消息出现为止,然后函数读取消息返回。

下面实例演示了消息队列在进程间的通信。程序中创建了一个消息的模板结构体,并对声明变量做初始化。使用msgget函数创建了一个消息队列,使用msgsnd函数向该队列中发送了一条消息。

(1)在vi编辑器中编辑该程序如下:

程序清单14-14  snd_msg.c 调用msgsnd函数向队列中发送消息

#include <sys/msg.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <stdio.h>

#include <stdlib.h>

struct msg{                     /*声明消息结构体*/

    long msg_types;             /*消息类型成员*/   

    char msg_buf[511];          /*消息*/

};

int main( void ) {

    int     qid;

    int         pid;

    int         len;

    struct msg pmsg;            /*一个消息的结构体变量*/

   

    pmsg.msg_types = getpid();  /*消息类型为当前进程的ID*/

    sprintf (pmsg.msg_buf,"hello!this is :%d/n/0", getpid() ); /*初始化消息*/

    len = strlen ( pmsg.msg_buf );   /*取得消息长度*/

   

    if ( (qid=msgget(IPC_PRIVATE, IPC_CREAT | 0666)) < 0 ) {  /*创建一个消
                                                                  息队列*/

        perror ( "msgget" );

        exit (1) ;

    }

   

    if ( (msgsnd(qid, &pmsg, len, 0 )) < 0 ){   /*向消息队列中发送消息*/

        perror ( "msgsn" );

        exit ( 1 );

    }

    printf ("successfully send a message to the queue: %d /n", qid);

    exit ( 0 ) ;

}

(2)在shell中编译该程序如下:

$gcc snd_msg.c –o snd_msg

(3)在shell中运行该程序如下:

$./ snd_msg

successfully send a message to the queue 0

上述程序中,先定义了一个消息的结构体。该结构体中包含两个成员,long类型成员msg_types是消息的类型,注意,在消息队列中是以消息类型做索引值来进行检索的。char类型数组存放消息。在程序中先声明了一个消息的结构体变量,并做相应初始化,然后使用了msgget函数创建一个消息队列,并将该消息发送到此消息队列中。以下是一个使用消息队列发送消息的程序。

下面实例演示了如何使用队列读取消息。在程序的开始部分,判断用户是否输入了目标消息队列ID,如果没有,则打印命令的帮助信息;如果用户输入了队列的ID,则从队列中取出该消息,并输出到标准输出。

(1)在vi编辑器中编辑该程序。

程序清单14-15  rcv_msg.c 使用msgrcv函数从指定队列中读出消息

#include <sys/msg.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <stdio.h>

#include <stdlib.h>

#define BUFSZ 4096

struct msg{             /*声明消息结构体*/

    long msg_types;     /*消息类型成员*/   

    char msg_buf[511];  /*消息*/

};

int main( int argc, char
* argv[] ) {

    int     qid;

    int         len;

    struct msg pmsg;

    if ( argc != 2 ){  /**/

        perror ( "USAGE: read_msg <queue ID>" );

        exit ( 1 );

    }

   

    qid = atoi ( argv[1] );   /*从命令行中获得消息队列的ID*/

    /*从指定队列读取消息
*/

    len = msgrcv ( qid, &pmsg, BUFSZ, 0, 0 );

   

    if ( len > 0 ){

        pmsg.msg_buf[len] = '/0';                       /*为消息添加结束符*/

        printf ("reading queue id :%05ld/n", qid ); /*输出队列ID*/

        /*该消息类型就是发送消息的进程ID*/

        printf ("message type : %05ld/n", pmsg.msg_types );

        printf ("message length : %d bytes/n", len );   /*消息长度*/

        printf ("mesage text: %s/n", pmsg.msg_buf); /*消息内容*/

    }

    else if ( len == 0 )

        printf ("have no message from queue %d/n", qid );

    else {

        perror ( "msgrcv");

        exit (1);

    }

    system("ipcs -q")  

    exit ( 0 ) ;

}

(2)在shell中编译该程序如下:

$gcc rcv_msg.c–o rcv _msg

(3)在shell中运行该程序如下:

$./ rcv_msg 0

reading queue id :0

message type : 03662

message length : 20 bytes

mesage text: hello!this is :3662

------ Message Queues --------

key          msqid      owner      perms      used-bytes   messages   

0x00000000  0          root        666         0            0          

该程序中声明了一个消息的结构体类型变量,并从命令行中得到所要操作的消息队列,然后使用函数msgrcv从指定消息队列中读取队列中最上面的一条消息(函数的第4个参数等于0,说明根据先进先出规则,应从队列的最上面读取一条消息),并将该消息输出到标准输出。在发送消息的程序中,消息类型字段指定的是发送消息进程的ID,可以使用该内容来判断信息的来源。

 

 

引用来自:Linux C程序设计大全

http://book.csdn.net/bookfiles/931/10093129302.shtml

抱歉!评论已关闭.