消息队列是消息的链接表,存放在内核中并由消息队列标识符标识。在本节中,我们把消息队列简称为队列(queue),其标识符为队列ID(queue ID)。
msgget用于创建一个新队列或打开一个现存的队列。msgsnd将新消息添加到队列尾端。每个消息包含一个正长整型类型字段,一个非负长度以及实际数据字节(对应于长度),所有这些都在将消息添加到队列时,传送给msgsnd。msgrcv用于从队列中取消息。我们并不一定要以先进先出次序取消息,也可以按消息的类型字段取消息。
每个队列都有一个msgqid_ds结构与其相关联:
struct msqid_ds { struct ipc_perm msg_perm; /* http://www.CUOXin.com/nufangrensheng/p/3561681.html */ msgqnum_t msg_qnum; /* # of messages on queue */ msglen_t msg_qbytes; /* max # of bytes no 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 */ ...};
此结构规定了队列的当前状态。结构中所示的各成员是由Single UNIX Specification定义的。具体实现可能包括标准中没有定义的另一些字段。
调用的第一个函数通常是msgget,其功能是打开一个现存队列或创建一个新队列。
#include <sys/msg.h>int msgget(key_t key, int flag);返回值:若成功则返回消息队列ID,若出错则返回-1
http://www.CUOXin.com/nufangrensheng/p/3561681.html中标识符和键部分,说明了将key变换成一个标识符的规则,并且讨论是否创建一个新队列或访问一个现存队列。
当创建一个新队列时,初始化msqid_ds结构的下列成员:
若执行成功,msgget返回非负队列ID。此后,该值就可被用于其他三个消息队列函数(msgsnd、msgrcv和msgctl)。
msgctl函数对队列执行多种操作。它和另外两个与信号量和共享存储有关的函数(semctl和shmctl)是XSI IPC的类似于ioctl的函数(亦即垃圾桶函数)。
#include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);返回值:若成功则返回0,若出错则返回-1
cmd参数说明对由msqid指定的队列要执行的命令:
IPC_STAT 取此队列的msqid_ds结构,并将它存放在buf指向的结构中。
IPC_SET 按由buf指定结构中的值,设置与此队列相关结构中的下列四个字段:msg_perm.uid、msg_perm.gid、msg_perm.mode和msg_qbytes。此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.cuid或msg_perm.uid;另一种是具有超级用户特权的进程。只有超级用户才能增加msg_qbytes的值。
IPC_RMID 从系统中删除该消息队列以及仍在该队列中的所有数据。这种删除立即生效。仍在使用这一消息队列的其他进程在它们下一次试图对此队列进行操作时,将出错返回EIDRM。此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.cuid或msg_perm.uid;另一种是具有超级用户特权的进程。
这三条命令(IPC_STAT、IPC_STAT和IPC_RMID)也可用于信号量和共享存储。
调用msgsnd将数据放到消息队列中。
#include <sys/msg.h>int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);返回值:若成功则返回0,若出错则返回-1
每个消息都由三部分组成,它们是:正长整型类型字段、非负长度(nbytes)以及实际数据字节(对应于长度)。消息总是放在队列尾端。
ptr参数指向一个长整型数,它包含了正的整型消息类型,在其后紧跟着消息数据。(若nbytes是0,则无消息数据。)若发送的最长消息是512字节,则可定义下列结构:
struct mymesg { long mtype; /* positive message type */ char mtext[512]; /* message data, of length nbytes */};
于是,ptr就是一个指向mymesg结构的指针。接收者可以使用消息类型以非先进先出的次序取消息。
参数flag的值可以指定为IPC_NOWAIT。这类似于文件I/O的非阻塞I/O标志(见http://www.CUOXin.com/nufangrensheng/p/3544997.html)。若消息队列已满(或者是队列中的消息总数等于系统限制值,或队列中的字节总数等于系统限制值),则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN。如果没有指定IPC_NOWAIT,则进程阻塞直到下述情况出现为止:有空间可以容纳要发送的消息;从系统中删除了此队列;或捕捉到一个信号,并从信号处理程序返回。在第二种情况下,返回EIDRM(“标识符被删除”)。最后一种情况则返回EINTR。
注意,对删除消息队列的处理不是很完善。因为对每个消息队列并没有设置一个引用计数器(对打开文件则有这种计数器),所以删除一个队列会造成仍在使用这一队列的进程在下次对队列进行操作时出错返回。信号量机制也以同样的方式处理其删除。相反,删除一个文件时,要等到使用该文件的最后一个进程关闭了它的文件描述符后,才能删除文件中的内容。
当msgsnd成功返回,与消息队列相关的msqid_ds结构得到更新,以表明发出该调用的进程ID(msg_lspid)、进行该调用的时间(msg_stime),并指示队列中增加了一条消息(msg_qnum)。
msgrcv从队列中取用消息:
#include <sys/msg.h>ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag);返回值:若成功则返回消息的数据部分的长度,若出错则返回-1
如同msgsnd中一样,ptr参数指向一个长整型数(返回的消息类型存放在其中),跟随其后的是存放实际消息数据的缓冲区。nbytes说明数据缓冲区的长度。若返回的消息大于nbytes,而且在flag中设置了MSG_NOERROR,则该消息被截短。(在这种情况下,不通知我们消息截短了,消息的截去部分被丢弃。)如果没有设置这一标志,而消息又太长,则出错返回E2BIG(消息仍留在队列中)。
参数type使我们可以指定想要哪一种消息:
type == 0 返回队列中的第一个消息。
type > 0 返回队列中消息类型为type的第一个消息。
type < 0 返回队列中消息类型值小于或等于type绝对值的消息,如果这种消息有若干个,则取类型值最小的消息。
type值非0用于以非先进先出次序读消息。例如,若应用程序对消息赋优先权,那么type就可以是优先权值。如果一个消息队列由多个客户进程和一个服务器进程使用,那么type字段可以用来包含客户进程的进程ID(只要进程ID可以存放在长整型中)。
可以指定flag值为IPC_NOWAIT,使操作不阻塞。这使得如果没有所指定类型的消息,则msgrcv返回-1,errno设置为ENOMSG。如果没有指定IPC_NOWAIT,则进程阻塞直至如下情况出现才终止:有了指定类型的消息;从系统中删除了此队列(出错则返回-1且errno设置为EIDRM);或捕捉到一个信号并从信号处理程序返回(msgrcv返回-1,errno设置为EINTR)。
msgrcv成功执行时,内核更新与该消息队列相关联的msqid_ds结构,以指示调用者的进程ID(msg_lrpid)和调用时间(msg_rtime),并将队列中的消息数(msg_qnum)减1。
消息队列原来的实施目的是提供比一般IPC更高速度的进程通信方法,但现在与其他形式的IPC相比,在速度方面已经没有什么差别了。考虑到使用消息队列具有的问题(见http://www.CUOXin.com/nufangrensheng/p/3561681.html中优点和缺点部分),我们得出的结论是,在新的应用程序中不应当再使用它们。
本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/。
新闻热点
疑难解答