消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。
同管道文件相比,消息队列中的每个消息指定特定的消息类型,接收的时候可以不需要按照队列次序读取,可以根据自定义类型接收特定类型的消息。我们可以将消息看成一个具有特定格式以及特定的优先级的一个记录,对消息队列有写权限的进程可以向消息队列中按照一定规则添加消息,对于消息队列有读权限的进程可以从消息队列中读取消息。
每个队列都有一个msgqid_ds结构与其相关联:
struct msqid_ds {
struct ipc_perm msg_perm;
msgqnum_t msg_qnum; /* # of messages on queue */ 0
msglen_t msg_qbytes; /* max # of bytes no queue */
pid_t msg_lspid; /* pid of last msgsnd() */ 0
pid_t msg_lrpid; /* pid of last msgrcv() */ 0
time_t msg_stime; /* last-msgsnd() time */ 0
time_t msg_rtime; /* last-msgrcv() time */ 0
time_t msg_ctime; /* last-change time */
...
};
此结构规定了队列的当前状态,在初始化的时候将msg_ctime设置为当前时间。msg_qbytes设置为系统限制值。
消息队列特点
- 消息队列是面向记录的,其中的消息具有特定的格式和特定的优先级
- 消息队列独立于发送与接收进程,即便进程终止时,消息队列及其内容都不会被改变
- 消息队列可以实现随机查询,不一定非得按照队列的先进先出获取消息,可以按照消息类型获取
消息队列的操作
1:消息队列的创建或获取
#include<sys/msg.h>
int msgget(key_t key,int flag); //打开一个现存消息队列或者创建一个新队列
//成功返回消息队列ID,失败返回-1
与其他IPC一样,程序必须提供一个以键来命名的消息队列 。flag是一个权限标志,与文件的访问权限一样,IPC_CREAT可与flag做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。
IPC_CREAT 如果不存在,则创建一个,否则直接打开已存在的
IPC_EXCL 只有在不存在的时候,新的才建立,否则就产生错误
2:把消息添加到消息队列中
#include<sys/msg.h>
int msgsnd(int msqid,const void *ptr, size_t size, int flag);
//成功返回0,出错返回-1
msqid是由msgget函数返回的消息队列标识符(id值)。ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体,在其后紧跟着消息数据。(若size是0,则无消息数据。)接收函数将用这个正的整形成员来确定消息的类型。若发送的最长消息是512字节,则可定义下列结构:
struct mymesq
{
long mtype; /*正的整数消息类型
char mtest[512]; /*消息数据,假设最长为512字节
}
size是ptr指向的消息的长度,不是整个结构体的长度,也就是说size是不包括长整型消息类型成员变量的长度。
flag用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。
参数flag的值可以置为为IPC_NOWAIT,这类似于文件I/O的非阻塞I/O标志。若消息队列已满(或者是队列中的消息总数等于系统限制值,或队列中的字节总数等于系统限制值),则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN。
如果没有指定IPC_NOWAIT,则进程阻塞直到下述情况出现为止:
- 有空间可以容纳要发送的消息
- 从系统中删除此队列;在这种情况下,返回EIDRM(标识符被删除)。
- 捕捉到一个信号,并从信号处理程序返回。这种情况则返回EINTR。
如果调用成功,消息数据的一份副本将被放到消息队列中,并返回0,失败时返回-1.
注意,对删除消息队列的处理不是很完善。因为对每个消息队列并没有设置一个引用计数器(对打开文件则有这种计数器),所以删除一个队列会造成仍在使用这一队列的进程在下次对队列进行操作时出错返回。信号量机制也以同样的方式处理其删除。相反,删除一个文件时,要等到使用该文件的最后一个进程关闭了它的文件描述符后,才能删除文件中的内容。
3:从一个消息队列获取消息
#include<sys/msg.h>
ssize_t msgrcv(int msqid,void *ptr,size_t nbytes,long type,int flag);
//成功返回消息部分的数据部分的长度,出错返回-1
msqid、ptr、nbytes的作用与函数msgsnd函数的一样。
type可以实现一种简单的接收优先级。
type=0,就获取队列中的第一个消息。
type>0,将获取具有相同消息类型的第一个信息。
type<0,就获取类型等于或小于type的绝对值的第一个消息。
type值非0用于以非先进先出次序读消息。例如,若应用程序对消息赋优先权,那么type就可以是优先权值
flag用于控制当队列中没有相应类型的消息可以接收时将发生的事情。可以指定flag值为IPC_NOWAIT,是操作不阻塞。这使得如果没有所指定类型的信息,则msgrcv返回-1,errno设置为ENOMSG
如果没有指定IPC_NOWAIT,则进程阻塞直到如下情况出现才终止:
- 有了指定类型的消息;
- 从系统中删除了此队列(出错则返回-1且errno设置为EIDRM);
- 捕捉到了信号并从信号处理程序返回(msgrcv返回-1,errno设置为EINTR)。
调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中(与普通文件操作非常相似),然后删除消息队列中的对应消息。失败时返回-1.
4:控制消息队列
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//若成功则返回0,若出错则返回-1
cmd参数说明对由msqid指定的队列要执行的命令:
IPC_STAT 取此队列的msqid_ds结构,并将它存放在buf指向的结构中(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)也可用于信号量和共享内存。
msgrcv成功执行时,内核更新与该消息队列相关联的msqid_ds结构,以指示调用者的进程ID(msg_lrpid)和调用时间(msg_rtime),并将队列中的消息数(msg_qnum)减1。
消息队列与有名管道的区别?
同:
- 消息队列与有名管道进行通信的进程都可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。
- 而且它们对每个发送或接收的数据都有一个最大长度的限制。
异:
- 在有名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。
- 有名管道必须依赖于发送进程与接收进程的存在,否则单一进程不能存在管道。消息队列可以独立于发送和接收进程而存在,从而消除了在同步有名管道的打开和关闭时可能产生的困难,这其实也是消息队列的优势。
- 消息队列同时通过发送消息还可以避免有名管道的同步和阻塞问题,它不需要由进程自己来提供同步方法。
-
接收程序可以通过消息类型有选择地接收数据,而不是像有名管道中那样,只能默认地接收。