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、消息队列是随内核存在的,即使进程退出它仍然存在。

2目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息队列,系统V消息队列目前被大量使用。考虑到程序的可移植性,新开发的应用程序应尽量使用POSIX消息队列。

3结构msg_queue用来描述消息队列头,存在于内核空间,而结构msqid_ds用来设置或返回消息队列的信息,存在于用户空间;上述两个结构体成员相似,且都定义在linux/msg.h中。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值