1 消息队列基本概念
消息队列是系统内核地址空间中的一个内部的链表。消息可以按照顺序发送到队列中,也可以以几种不同的方式从队列中读取。每一个消息队列用一个唯一的IPC标识符表示。
了解在系统内核中的数据结构是了解IPC机制如何工作的最好的方法。
首先我们看一下数据结构msgbuf。此数据结构可以说是消息数据的模板。虽然此数据结构需要用户自己定义,但了解系统中有这样一个数据结构是十分重要的。在<sys/msg.h>中,此数据结构是这样定义的:
- struct msgbuf{
- long mtype;
- char mtext[1];
- };
在数据结构msgbuf中共有两个元素:
mtype指消息的类型,它由一个整数来代表,并且它只能是大于0的整数。
mtext是消息数据本身。
mtext字段不但可以存储字符,还可以存储任何其他的数据类型。此字段可以说是完全任意的,因为程序员自己可以重新定义此数据结构。请看下面重新定义的例子:
- struct my_msgbuf{
- long mtype;
- char request_id;
- struct client info;
- };
这里的消息类型字段和前面的一样,但数据结构的其余部分则由其他的两个字段所代替,而其中的一个还是另外一个结构。这就体现了消息队列的灵活之处。内核本身并不对消息结构中的数据做任何翻译。你可以在其中发送任何信息,但存在一个内部给定的消息大小的限制。在Linux系统中,消息的最大的长度是4056个字节,其中包括mtype,它占用4个字节的长度。
创建消息队列
系统调用msgget()用于创建一个新的消息队列,或者存取一个已经存在的消息队列,其函数原型是:
- #include<sys/types.h>
- #include<sys/ipc.h>
- #include<sys/msg.h>
- int msgget(key_t key, int msgflg);
系统调用msgget()中的第一个参数是消息队列关键字值,可以由ftok()获得。第二个参数msgflg是一些标志,包括:
IPC_CREAT:如果内核中没有此队列,则创建它。
IPC_EXCL:当和IPC_CREAT一起使用时,如果队列已经存在,则返回错误。
当msgget()执行成功时,返回消息队列的标识符,否则返回-1,通过errno和perror()函数查看错误信息.
下面是一个打开和创建一个消息队列的例子,函数返回消息队列的标识符
- int open_ queue(key_ t keyval)
- {
- int qid;
- if((qid = msgget(keyval,IPC_CREAT|0660)) == -1){
- perror(”msgget”);
- return(-1);
- }
- return(qid);
- }
发送和接收消息
当得到了消息队列标识符,就可以在队列上执行发送或者接收消息了。msgsnd()系统调用用于向队列发送一条消息,其函数原型是:
- int msgsnd(int msqid, struct msgbuf *msgp, sizet msgsz, int msgflg);
第一个参数是消息队列标识符。第二个参数msgp,是指向消息缓冲区的指针。参数msgsz指定了消息的字节大小,但不包括消息类型的长度(4个字节)。参数msgflg可以设置为:
0:此时为忽略此参数,如果消息队列已满,调用进程将会挂起,直到消息可以写入到队列中。
IPC_NOWAIT:如果消息队列己满,那么此消息则不会写入到消息队列中,控制将返回到调用进程中。消息队列写入成功时,函数返回0,否则返回-1。
下面是一个发送消息的例子:
- int send_message(int qid, struct mymsgbuf *qbuf)
- {
- int result, length;
-
- length = sizeof(struct mymsgbuf) - sizeof(long);
- if((result = msgsnd(qid, qbuf, length, O)) == -1){
- perror(”msgsnd”);
- return(-1);
- }
- return(result);
- }
msgrcv()系统调用用于从消息队列读取一条消息,其函数原型是:
- ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtype, int msgflg);
第一个参数是消息队列的标识符。第二个参数代表要存储消息的缓冲区的地址。第三参数是消息缓冲区的长度,不包括mtype的长度,它可以按照如下的方法计算:
msgsz=sizeof(struct mymsgbuf)-sizeof(long);
第四个参数是要从消息队列中读取的消息的类型。
如果msgtype=0,接收消息队列的第一个消息。大于0接收队列中消息类型等于这个值的第一个消息。小于0接收消息队列中小于或者等于msgtype绝对值的所有消息中的最小一个消息。一般为0。
第五个参数msgflg取值为:
0:从队列中取出最长时间的一条消息。
IPC_NOWAIT:当队列没有消息时,调用会立即返回ENOMSG错误。否则,调用进程将会挂起,直到队列中的一条消息满足msgrcv()的参数要求。
当函数成功时,返回写入缓冲区的数据大小,否则返回-1。
下面是一个接收消息的例子:
- int read_message(int qid, long type, struct mymsgbuf *qbuf)
- {
- int result, length;
-
- length = sizeof(struct mymsgbuf) - sizeof(long);
- if((result = msgrcv(qid, qbuf, length, type, O))==-l){
- perror(¨msgrcv¨);
- return(-1);
- }
- return(result);
- }
消息队列的控制
消息队列标识符的属性被记录在一个msgid_ds结构体:
- struct msqid_ds{
- struct ipc_perm msg_perm;
- time_t msg_stime;
- time_t msg_rtime;
- time_t msg_ctime;
- unsigned long __msg_cbytes;
- msgqnum_t msg_qnum;
- msglen_t msg_qbytes;
- pid_t msg_lspid;
- pid_t msg_lrpid;
- }
通过msgctl()可以对消息队列进行控制或者一些属性的修改,其函数原型为:
- int msgctl(int msqid, int cmd, struct msqid ds *buf;
第一个参数是消息队列的标识符,第二个参数cmd指定了操作,下面是几个常用的操作:
IPC_STAT:读取消息队列的数据结构msqid_ds,并将其存储在buf指定的地址中。
IPC_SET:设置消息队列的数据结构msqid_ds中的ipc_perm、msg_qbytes、msg_ctime元素的值。这个值取自buf参数。
IPC_RMID:从系统内核中移走消息队列。
比如下面是一个删除消息队列的例子。
- int remove_queue(int qid){
- if(msgctl(qid, IPC_RMID, 0)==-1){
- perror(”msgctl”);
- return(-1);
- }
- return(O);
- }
综合示例msgtool
我们来实现一个简单的消息队列工具,用于创建消息队列、发送、读取消息、改变权限以及删除消息队列。
它的用法如下:
(1) 发送消息
(2) 读取消息
(3) 改变权限
(4) 删除队列
msgtool d
源代码msgtoo1.c
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <ctype.h>
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
-
- #define MAX_SEND_SIZE 80
- struct mymsgbuf {
- long mtype;
- char mtext[MAX_SEND_SIZE];
- };
-
- void send_message(int qid, struct mymsgbuf *qbuf, long type, char *text);
- void read_message(int qid, struct mymsgbuf *qbuf, long type);
- void remove_queue(int qid);
- void change_queue_mode(int qid, char *mode);
- void usage(void);
-
- int main(int argc, char *argv[])
- {
- key_t key;
- int msgqueue_id;
- struct mymsgbuf qbuf;
- if(argc == 1)
- usage();
-
- key = ftok(".", 'm');
-
- if((msgqueue_id = msgget(key, IPC_CREAT|0660)) == -1) {
- perror("msgget");
- exit(1);
- }
-
- printf("message queue id = [%d]\n", msgqueue_id );
-
- switch(tolower(argv[1][0]))
- {
- case 's':
- if( argc < 4 ){
- usage();
- break;
- }
- send_message(msgqueue_id, (struct mymsgbuf *)&qbuf, atol(argv[2]), argv[3]);
- break;
- case 'r':
- if( argc < 3 ){
- usage();
- break;
- }
- read_message(msgqueue_id, &qbuf, atol(argv[2]));
- break;
- case 'd':
- remove_queue(msgqueue_id);
- break;
- case 'm':
- if( argc < 3 ){
- usage();
- break;
- }
- change_queue_mode(msgqueue_id, argv[2]);
- break;
- default: usage();
- }
- return(0);
- }
-
- void send_message(int qid, struct mymsgbuf *qbuf, long type, char *text)
- {
-
- printf("Sending a message ...\n");
- qbuf->mtype = type;
- strcpy(qbuf->mtext, text);
- if((msgsnd(qid, (struct msgbuf *)qbuf, strlen(qbuf->mtext)+1, 0)) ==-1)
- {
- perror("msgsnd");
- exit(1);
- }
- }
-
- void read_message(int qid, struct mymsgbuf *qbuf, long type)
- {
-
- printf("Reading a message ...\n");
- qbuf->mtype = type;
- msgrcv(qid, (struct msgbuf *)qbuf, MAX_SEND_SIZE, type, 0);
- printf("Type: %ld Text: %s\n", qbuf->mtype, qbuf->mtext);
- }
-
- void remove_queue(int qid)
- {
-
- msgctl(qid, IPC_RMID, 0);
- }
-
- void change_queue_mode(int qid, char *mode)
- {
- struct msqid_ds myqueue_ds;
-
- msgctl(qid, IPC_STAT, &myqueue_ds);
-
- sscanf(mode, "%ho", &myqueue_ds.msg_perm.mode);
-
- msgctl(qid, IPC_SET, &myqueue_ds);
- }
-
- void usage(void)
- {
- fprintf(stderr, "msgtool - A utility for tinkering with msg queues\n");
- fprintf(stderr, "USAGE: msgtool (s)end <type> <messagetext>\n");
- fprintf(stderr, " (r)ecv <type>\n");
- fprintf(stderr, " (d)elete\n");
- fprintf(stderr, " (m)ode <octal mode>\n");
- exit(1);
- }
我们先来发送一条消息:
- ./msgtool s 1 ”Hello”
- message queue id = [32768]
- Sending a message...
用ipcs命令可以看到创建的消息队列:
- ipcs -q
-
- ------ Message Queues --------
- key msqid owner perms used-bytes messages
- 0x6d01c86f 32768 user 660 6 1
可以看到现在队列里有1条消息。
再用msgtool读出先读出一条消息,然后再次读取消息:
- ./msgtool r 0
- message queue id = [32768]
- Reading a message ...
- Type: 1 Text: Hello
- [alex@alex-/work/tutorai l]$./msgtool r 0
- message queue id 2[32768]
- Reading a message...
此时消息队列已经为空,进程被阻塞等待消息。我们到另外一个终端发送一条消息:
- ./msgtool s 2 “Another message”
- message queue id=[32768]
- Sending a message…
这是第一个终端的msgtool收到消息:
Type:2 Text:Another message
下面我们修改消息队列的访问权限
- ./msgtool m 600
- message queue id=[32768]
- [alex@alex一/work/tutorail]$ ipcs –q
-
- ------ Message Queues --------
- key msqid owner perms used-bytes messages
- 0x6d01c86f 32768 user 660 6 1
从ipcs命令看到的消息队列中,perms列已经被改为600。最后删除消息队列
- /msgtool d
- message queue id=[32768]
- [alex@alex一/work/tutorail]$ ipcs –q
-
- ------ Message Queues --------
- key msqid owner perms used-bytes messages