1. 消息队列
1.1消息队列
消息队列是消息的链接表,存放着内核中由消息队列标识符标识。消息队列简称队列(queue),其标识符为队列ID(queue ID)。
每个队列都有一个msqid_ds结构与其相关联:
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_rtime; // last-msgsnd time
time_t msg_rtime; // last-msgrcv time
time_t msg_ctime; // last-change time
.
.
.
}
msgget用于创建一个新队列或打开一个现存的队列。msgsnd将新消息添加到队列尾端。每个消息包含一个正长整型类型字段,一个非负长度以及实际数据字节(对应于长度),所以这些都在将消息添加到队列时,传送给msgsnd。Msgrcv用于从队列中取消息。我们并不一定要以先进先出次序取消息,也可以按消息的类型字段取消息。
调用的第一个函数通常是msgget,其功能是打开一个现存队列或创建一个新队列。
#include <sys/msg.h>
int msgget(key_t key, int flag);
若执行成功,msgget返回非负队列ID,此值就可被用于其它三个消息队列函数。
函数msgctl对队列执行多种操作。它和另外两个与信号量和共享内存有关的函数(semctl和shmctl)是XSI IPC的类似于ioctl的函数。
#include <sys/msg.h>
int msgctl (int msqid, int cmd, struct msqid_ds *buf);
参数cmd说明对由msqid指定的队列要执行的命令:
IPC_STAT 取此队列的msqid_ds结构,并将它存放着buf指向的结构中。
IPC_SET 按由buf指向结构中的值,设置与此队列相关结构中的下列四个字段:msg_perm.uid、msg_perm.gid、msg_perm.mode和msg_qbtes。此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.cuid或msg_perm.uid;另一种是具有超级用户特权的进程。只有超级用户才能增加msg_gbytes的值。
IPC_RMID 从系统中删除该消息队列以及仍在该队列中的所有数据。这种删除立即生效。仍在使用这一消息队列的其它进程在它们下一次试图对此队列进行操作时,将出错返回EIDRM。此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.cuid或msg_perm.uid;另一种是具有超级用户特权的进程。只有超级用户才能增加msg_gbytes的值。
调用msgsnd将数据放到消息队列中。
#include <sys/msg.h>
int msgsnd (int msqid, const void *ptr, size_t nbytes, int flag);
我们知道每个消息由三部分组成:正常正行字段、非负长度(nbytes)、以及实际数据字节(对应于长度)。消息总是放在队列尾端。
参数ptr指向一个长整形数,它包含了正的整型消息类型,在其后紧跟着消息数据。(若nbytes是0,则无消息数据)。若发送的最长消息是256字节,则可以定义下列结构:
struct mymsg{
long type; //消息类型
char data[256]; //消息数据和长度
};
于是,ptr就是一个指向mymsg结构的指针。
参数flag的值可以指定为IPC_NOWAIT。这类似于文件I/O的非阻塞I/O标志。若消息队列已满(或是队列中的消息总数等于系统的限制,或是队列中的字节总数等于系统的限制),则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN。如果没有指定IPC_NOWAIT,则进程阻塞直到下述情况出现为止:有空间可以容纳要发送的消息;从系统中删除了此队列(EIDRM);或是捕捉到一个信号,并从信号处理程序返回(EINTR)。
注意,删除队列使得仍在使用这一消息队列的其它进程在它们下一次试图对此队列进行操作时,将出错返回EIDRM。信号机制也以同样的方式处理其删除。相反,删除一个文件时,要等到使用该文件的最后一个进程关闭了它的文件描述符后,才能删除该文件的内容。
当msgsnd成功返回,与消息队列相关的msqid_ds结构得到更新,以表明发出该调用的进程ID(msg_lspid)、进行内核调用的时间(msg_stime),并指示队列中增加了一条消息(msg-qnum)。
函数msgrcv从队列中取消息:
ssize_t msgrcv(int msqid, viod *ptr, size_t nbytes, long type, int flag);
如同msgsnd中一样,ptr参数指向一个长整型数(返回的消息类型存放在其中),跟随其后的是存放实际消息数据的缓冲区。Nbytes说明数据缓冲区的长度。若返回的消息大于nbytes,而且在flag中设置了MSG_NOERROR,则该消息被截断。如果没有设置这一标识,而且消息又太长,则出错返回E2BIG(消息仍留在队列中)。
参数type使我们可以指定想要哪种消息:
type == 0 返回队列中的第一个消息
type > 0 返回队列中消息类型为type的第一个消息
type < 0 返回队列中消息类型小于或等于type绝对值的消息,如果这种消息有若干个,则取类型最小的消息。
参数type非0用于以非先进先出的次序读消息。
参数flag可以指定IPC_NOWAIT,使操作不阻塞。这使得如果没有指定类型的消息,则msgrcv返回-1;errno设置为ENOMSG。如果没有指定IPC_NOWAIT,则进程阻塞直至如下情况出现才终止:有了指定类型的消息,从系统中删除此队列(出错返回-1则errno设置为EIDRM),或者捕捉一个信号从信号处理程序返回(msgrcg返回-1,则errno设置为EINTR)。
当msgrcv成功执行时,内核更新与该消息队列相关联的msqid_ds结构,以指示调用者的进程ID(msg_lrpid)和调用时间(msg_rtime),并将队列中的消息数(msg_qnum)减一。
1.2. 实例
下面是两个小程序:msgrcv.c用于接收消息,msgsnd用于发送消息。这两个都可以创建消息队列,但只有接收者在收到最后一个消息之后可以删除这个消息队列。执行结果如下:
#./msgsnd
Please input data: hello
Please input data: how are you
Please input data: end
#./msgrcv
receive data: hello
receive data: how are you
receive data: end
#
源程序如下: