1.引言
消息队列是消息的链接表,存放在内核中并由消息队列标识符标识。
2.创建消息队列
每一个队列都有一个msqid_ds结构与其相关联。此结构规定了消息队列的当前状态。主要包括XSI IPC共同的信息ipc_perm,队列长度,队列中消息数,和最后对队列进行操作的进程及时间。
struct msqid_ds {
struct ipc_perm msg_perm;
msgqnum_t msg_qnum; /* # of messages on queue */
msglen_t msg_qbytes; /* max # of bytes on queue */
pid_t msg_lspid; /* pid of last msgsnd() */
pid_t msg_lrpid; /* pid of last msgrcv() */
time_t msg_stime; /* last-msgsnd() time */
time_t msg_rtime; /* last-msgrcv() time */
time_t msg_ctime; /* last-change time */
.
.
.
};
使用消息队列第一个要用到的函数就是msgget,msget函数用于创建一个新队列或打开一个现存的队列。
#include <sys/msg.h>
int msgget(key_t key, int flag);
//返回值:成功则返回消息队列ID,出错返回-1
对消息队列的操作需要使用,消息队列ID。
3.操作消息队列
msgctl函数对消息队列执行多种操作。
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf );
//成功为0,出错为-1
cmd参数说明对由msgid指定的消息队列要执行的命令。
IPC_STAT,取此消息队列的msqid_ds结构,并将其放到buf指向的结构中。
IPC_SET,按由buf指向结构中的值,设置与此队列相关结构中的四个字段:msg_perm.uid、msg_perm.gid、msg_perm.mode和msg_qbytes。调用进程的有效用户ID必须等于msg_perm.cuid或msd_perm.uid,或者是具有超级用户权限的进程。只有超级用户才能增加msg_qbytes的值。
IPC_RMID,从系统中删除该消息队列以及仍在队列中的所有数据。(XSI IPC都是没有引用计数的!!!)这种删除立即生效。调用进程的有效用户ID必须等于msg_perm.cuid或msd_perm.uid,或者是具有超级用户权限的进程。
这三条命令(IPC_STAT、IPC_SET、IPC_RMID)也可用于信号量或者共享存储。
4.发送消息
msgsnd函数可将数据发送到消息队列尾端。
#include <sys/msg.h>
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);
//成功为0,出错为-1
每一个消息由三部分组成:正长整型的类型字段mtype、非负长度nbytes以及实际数据字节(对应于长度)。参数ptr是一个结构的指针,它包含了消息类型mtype和消息数据。这样接受者可以使用消息类型以非先进先出的次序取消息。
struct mymesg {
long mtype; /* positive message type */
char mtext[nbytes]; /* message data, of length nbytes */
};
当msgsnd成功返回,与消息队列关联的msqid_ds结构得到更新,以标明发出该调用的进程ID(msg_lspid)、进行该调用的时间(msg_stime),并指示队列中增加了一条消息(msg_qnum)。
5.接收消息
msgrcv函数可从消息队列中取得消息。
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag);
//若成功则返回消息的数据部分的长度,出错返回-1
参数ptr与msgsnd中的一样。nbytes说明数据缓冲区的长度。若返回的消息大于nbytes,而且在flag中设置了MSG_NOERROR,则该消息被截断。在这种情况下,消息的截断部分被丢弃。如果没有设置这一标志,而消息又太长,则出错返回E2BIG,消息仍然留在队列中。
参数type指定想要接收哪一种消息:1)type==0,返回队列中的第一个消息;2)type>0,返回队列中消息类型为type的第一个消息;3)type<0,返回队列中消息类型值小于或等于type绝对值的消息,如果这种消息有若干个,则取类型值最小的消息。这就可以实现以非先进先出的方式操作消息队列!!!
msgrcv成功执行时,内核更新与消息队列关联的msqid_ds结构,以指示调用者的ID(msg_lrpid)和调用时间(msg_rtime),并将队列中的消息数(msg_qnum)减1。
6.消息队列的劣势
消息队列原来的实施目的就是提供比一般IPC更高速度的进程通信方式,不过现在与其他IPC相比在速度上没有什么区别了,甚至STREAM管道要比消息队列更加快。而且消息队列具有XSI IPC共有的问题,所以一般不采用消息队列作为进程间通信的手段。
7.示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <time.h>
#include "apue.h"
struct mymesg
{
long mtype;
char mtext[6];
};
void print_msqid(const struct msqid_ds *ds)
{
printf("uid=%d, gid=%d, cuid=%d, cgid=%d, mode=%o/n",
ds->msg_perm.uid, ds->msg_perm.gid, ds->msg_perm.cuid,
ds->msg_perm.cgid, ds->msg_perm.mode);
printf("msg_qnum=%d, msg_qbytes=%d/n",
(int)ds->msg_qnum, (int)ds->msg_qbytes);
printf("msg_lspid=%d, msg_lrpid=%d/n",
ds->msg_lspid, ds->msg_lrpid);
printf("msg_stime=%smsg_rtime=%smsg_ctime=%s",
ctime(&ds->msg_stime), ctime(&ds->msg_rtime), ctime(&ds->msg_ctime));
}
int main()
{
pid_t pid;
key_t key;
int msgid;
struct mymesg snd_msg={1, "hello"};
struct mymesg rcv_msg;
struct msqid_ds msqds;
if((key = ftok("temp", 'b')) == -1)
err_sys("ftok error");
if((pid = fork()) < 0)
err_sys("fork error");
else if(pid == 0) /*parent*/
{
if((msgid = msgget(key, IPC_CREAT | 00666)) == -1)
err_sys("create msgqueue error");
if(msgsnd(msgid, &snd_msg, 6, 0) == -1)
err_sys("msgsnd error");
exit(0);
}
if(waitpid(pid, NULL, 0) == -1)
err_sys("waitpid error");
if((msgid = msgget(key, 0)) == -1)
err_sys("open msgqueue error");
if(msgrcv(msgid, &rcv_msg, 6, 1, 0) == -1)
err_sys("msgrcv error");
fputs(rcv_msg.mtext, stdout);
fputc('/n', stdout);
if(msgctl(msgid, IPC_STAT, &msqds) == -1)
err_sys("msgctl IPC_STAT error");
print_msqid(&msqds);
if(msgctl(msgid, IPC_RMID, NULL) == -1)
err_sys("msgctl IPC_RMID error");
exit(0);
}