Linux进程间通讯之消息队列

1. 基本概念

消息队列的最佳定义是:内核地址空间中的内部链表。消息可以顺序地发送到队列中,

并以几种不同的方式从队列中获取。当然,每个消息队列都是由 IPC标识符所唯一标识的。

2. 内部和用户数据结构

要完成理解象系统 V IPC这样复杂的问题,关键是要彻底熟悉内核的几个内部数据结构。

甚至对那些最基本的操作来说,直接访问这些结构中的某几个结构也是必要的,而其他的结

构则停留在一个更低的级别上。

3. 消息缓冲区

我们要介绍的第一个结构是 msgbuf结构。这个特殊的数据结构可以认为是消息数据的模

板。虽然定义这种类型的数据结构是程序员的职责,但是读者绝对必须知道实际上存在

msgbuf类型的结构。它是在在 linux/msg.h中定义的,有2个成员:

• mtype它是消息类型,以正数来表示。这个数必须为一个正数!

• mtext它就是消息数据。

 

4. 内核msg结构

内核把消息队列中的每个消息都存放在 msg结构的框架中。该结构是在 linux/msg.h中定义

的,如下是其成员的描述:

• msg_next这是一个指针,指向消息队列中的下一个消息。在内核寻址空间中,它们

是当作一个链表存储的。

• msg_type这是消息类型,它的值是在用户结构 msgbuf中赋予的。

• msg_spot这是一个指针,指向消息体的开始处。

• msg_ts这是消息文本(消息体)的长度。

内核 msgid_ds结构IPC对象分为三类,每一类都有一个内部数据结构,该数据结构

是由内核维护的。对于消息队列而言,它的内部数据结构是               msqid_ds结构。对于系统上

创建的每个消息队列,内核均为其创建、存储和维护该结构的一个实例。该结构在

linux/msg.h中定义,如下所示:

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成员在不同的系统也会有不同的上限值,这里就不逐一介绍了,详细内容请参阅相关系统手册。

• msg_perm它是 ipc_perm结构的一个实例, ipc_perm结构是在 linux/ipc.h中定义的。

该成员存放的是消息队列的许可权限信息,其中包括访问许可信息,以及队列的创建者

 

的有关信息 (uid等等)

• msg_first链接到队列中的第一个消息 (列表头部 )

• msg_last链接到队列中的最后一个消息 (列表尾部)

• msg_stime发送到队列的最后一个消息的时间戳 (time_t)

• msg_rtime从队列中获取的最后一个消息的时间戳。

• msg_ctime对队列进行最后一次变动的时间戳。

 

 

5. 内核ipc_perm结构

内核把IPC对象的许可权限信息存放在 ipc_perm类型的结构中。例如在前面描述的某个消

息队列的内部结构中, msg_perm成员就是 ipc_perm类型的,它的定义是在文件 linux/ipc.h中,

 

以上所有的成员都具有相当的自扩展性。对象的创建者以及所有者 (它们可能会有不同 )

有关信息,以及对象的 IPC关键字都是存放在该结构中的。八进制形式的访问模式也是存放在

这里的,它是以一种无符号短整型的形式存储的。最后,时间片使用序列编号存放在最后面,

每次通过系统调用关闭 IPC对象(摧毁)时,这个值将被增加一,至多可以增加到能驻留在系统

中的IPC对象的最大数目。用户需要关心这个值吗?答案是

有关这个问题,在 Richard Stevens所著的《 Unix Network Programming 》一书的第125

中作了精辟的讨论。该书还介绍了 ipc_perm结构的存在和行为在安全性方面的原因。

 

6. 创建消息队列:

1 msgget简介:为了创建一个新的消息队列,或者访问一个现有的队列,可以使用系统调用 msgget ( )

 

msgget ( )的第一个变元是关键字的值 (在我们的例子中该值是调用 ftok ( )的返回值)。这个

关键字的值将被拿来与内核中其他消息队列的现有关键字值相比较。比较之后,打开或者访

问操作依赖于msgflg变元的内容。

• IPC_CREAT如果在内核中不存在该队列,则创建它。

• IPC_EXCL当与IPC_CREAT一起使用时,如果队列早已存在则将出错。

如果只使用了 IPC_CREAT, msgget ( )或者返回新创建消息队列的消息队列标识符,或者

 

会返回现有的具有同一个关键字值的队列的标识符。如果同时使用了

 

I P C _ E X C L IPC_CREAT,那么将可能会有两个结果。或者创建一个新的队列,或者如果该队列存在,则

调用将出错,并返回-1IPC_EXCL本身是没有什么用处的,但在与IPC_CREAT组合使用时,

它可以用于保证没有一个现存的队列为了访问而被打开。

有个可选的八进制许可模式,它是与掩码进行 OR操作以后得到的。这是因为从功能上讲,

每个IPC对象的访问许可权限与 Unix文件系统的文件许可权限是相似的!

 

2msgget举例:

下面实例演示了使用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段标明的是IPCkey值,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.uidmsg_perm.gidmsg_perm.mode以及msg_perm.cuid。该命令只能由具有以下条件的进程执行:进程有效用户ID等于msg_perm.cuidmsg_perm.uid超级用户进程。其中只有超级用户才可以增加队列的msg_qbytes的值

IPC_RMID

删除队列,并清除队列中的所有消息。此操作会影响后续进程对这个队列的相关操作。该命令只能由具有以下条件的进程执行。进程有效用户ID等于msg_perm.cuidmsg_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

------ 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  

 

 

7.  读写消息队列

一旦获得了队列标识符,用户就可以开始在该消息队列上执行相关操作了。为了向队列

传递消息,用户可以使用 msgsnd系统调用.

由于消息队列的特殊性,系统为这个数据类型提供了两个接口(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并设置错误变量errnoerrno可能出现的值有:EAGAINEACCESEFAULTEIDRMEINTREINVALENOMEM。当函数成功返回后会更新相应队列的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,可以使用该内容来判断信息的来源。

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liranke

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值