消息队列是消息的链接表,存放在内核中并有消息队列标识符标识。每个消息队列有一个称为 key的名称,如同用户文件描述符一样,每个Unix消息队列还有一个消息队列描述符。
- 消息的结构
每个消息包含一个正长整型字段,一个非负长度以及实际的数据字节,所有这些都在将消息添加到队列时,传送给msgsnd(发送消息函数)。msgrcv用于从消息对垒中取消息。可以不按照先进先出的次序取消息,也可以按照消息的类型字段取消息。
每个队列都有个msqid_qs结构与其关联,该结构规定了队列当前的状态。
struct msqid_ds
{
struct ipc_perm msg_perm; /* */
msgqnum_t msg_qnum; /* # of message 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 */
...
}
1. 消息队列建立
#include <sys/msg.h>
int msgget(key_t key, int flag); //返回值:若成功则返回消息队列ID,若出错则返回-1
第一个参数 key 是消息队列对象的关键字(key),函数将它与已有的消息队列对象的关键字进行比较来判断消息队列对象是否已经创建。
第二个参数 flag:
IPC_CREAT : 如果消息队列对象不存在,则创建之,否则则进行打开操作;
IPC_EXCL: 和IPC_CREAT 一起使用(用”|”连接),如果消息对象不存在则创建之,否则产生一个错误并返回。
如果单独使用IPC_CREAT 标志,msgget()函数要么返回一个已经存在的消息队列对象的标识符,要么返回一个新建立的消息队列对象的标识符。
如果将IPC_CREAT 和IPC_EXCL标志一起使用,msgget()将返回一个新建的消息对象的标识符,或者返回-1 如果消息队列对象已存在。
IPC_EXCL 标志本身并没有太大的意义,但和IPC_CREAT 标志一起使用可以用来保证所得的消息队列对象是新创建的而不是打开的已有的对象。
除了以上的两个标志以外,在msgflg 标志中还可以有存取权限控制符。这种控制符的意义和文件系统中的权限控制符是类似的。
2. 消息队列的发送
#include <sys/msg.h>
int msgsnd( int msqid; const void *ptr, size_t nbytes, int flag ); //返回值: 若成功返回0, 若出错则返回 -1
第一个参数 msqid : 消息队列标识
第二个参数 ptr: ptr参数指向一个消息结构体, 正整数标识消息类型,其后跟随者消息数据,消息长度的最大长度可以自定义,但系统有限制,不可过大。
struct mymesg
{
long mtype; /* positive message type */
char mtext[MAXMSGLENGTH]; /* message data, of length nbytes */
};
第三个参数 nbytes : 消息的大小, 其值应该大于等于mymesg中消息的最大长度
第四个参数 flag : 0,表示忽略;IPC_NOWAIT,如果消息队列已满,则返回一个EAGAIN,并将控制权交回调用函数的进程。
如果不指定这个参数,那么进程将被阻塞直到函数正常发送完消息为止。
当出错时返回-1时, 会设置errno(需要include <errno.h>),值意义如下,其解释可以使用strerror(errno) 获取并依据其进行逻辑判断或输出到日志中:
EACCES:调用进程在消息队列上没有写权能,同时没有CAP_IPC_OWNER权能
EAGAIN:由于消息队列的msg_qbytes的限制和msgflg中指定IPC_NOWAIT标志,消息不能被发送
EFAULT:msgp指针指向的内存空间不可访问
EIDRM: 消息队列已被删除
EINTR: 等待消息队列空间可用时被信号中断
EINVAL:参数无效
ENOMEM:系统内存不足,无法将msgp指向的消息拷贝进来
3. 消息队列的接收
#include <sys/msg.h>
ssize_t msgrcv( int msqid, void *ptr; size_t nbytes, long type, int flag) ; //返回值: 若成功则返回消息的数据部分长度, 若出错则返回 -1
第一个参数 msqid : 消息队列标识
第二个参数 ptr: 消息结构体,具体参考发送函数第二个参数
第三个参数 nbytes : 消息的大小, 其值应该大于等于mymesg中消息的最大长度
第四个参数 type :0 返回队列中第一个消息
>0 返回队列中消息类型为type的第一个消息
<0 返回队列中消息类型小于或等于type绝对值的消息,如果这种消息有若干个,则返回类型最小的消息
type值非0用于非先进先出读取消息,例如:若应用程序对消息赋优先权,那么type就可以是优先值。
如果一个消息队列由多个客户端和一个服务器进程使用,那么type字段可以用来包含客户进程ID。
第五个参数 flag: 0,表示忽略;
IPC_NOWAIT,如果消息队列为空,则返回一个ENOMSG,并将控制权交回调用函数的进程。
如果不指定这个参数,那么进程将被阻塞直到函数可以从队列中得到符合条件的消息为止。
当出错时返回-1时, 会设置errno
E2BIG:消息文本长度大于msgsz,并且msgflg中没有指定MSG_NOERROR
EACCES:调用进程没有读权能,同时没具有CAP_IPC_OWNER权能
EAGAIN:消息队列为空,并且msgflg中没有指定IPC_NOWAIT
EFAULT:msgp指向的空间不可访问
EIDRM:当进程睡眠等待接收消息时,消息已被删除
EINTR:当进程睡眠等待接收消息时,被信号中断
EINVAL:参数无效
ENOMSG:msgflg中指定了IPC_NOWAIT,同时所请求类型的消息不存在
4. 消息队列的操纵
#include <sys/msg.h>
int msgctl( int msqid, int cmd, struct msqid_ds *buf) //返回值 : 若成功则返回0 ,若出错则返回 -1
第一个参数 msqid : 消息队列标识
第二个参数 cmd:对消息队列要执行的指令
IPC_STAT: 取吃队列的msqid_t结构,并将它存放到buf指向的结构体中去。
IPC_SET :按有buf指向结构九中的值,设置与此队列相关的结构体重的下列四个字段
msg_perm.uid、msg_perm.gid、msg_perm.mod和msg_qbytes。
此命令只能有下列两种操作执行:一种是其有效用户ID等于msg_perm.cuid或msg_poerm.uid;另一种是具有超级用户特权的进程。
只有超级用户才能增加msg_qbytes的值。
IPC_RMID: 从系统中删除该消息队列以及仍在该队列中的所有消息数据。这种删除立即生效。
仍在使用这一消息队列的其他进程在它们下一次视图对该消息队列进程操作时,将出错,并设置errno为EIDRM。
此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_per.cuid或者msg_perm.uid;另一种是超级用户进程。
当出错时返回-1时, 会设置errno
EACCES 没有读的权限同时cmd 是IPC_STAT
EFAULT buf 指向的地址无效
EIDRM 在读取中队列被删除
EINVAL msgqid无效, 或者msgsz 小于0
EPERM IPC_SET或者IPC_RMID 命令被使用,但调用程序没有写的权限