说明:只供学习交流,装载请注明出处
一,概念
消息队列实际上就是一个消息链表,而消息是链表中具有特定格式及优先级的记录。进程按照一定的规则在消息链表中添加新的消息,而需要消息的进程可以从消息队列中获得所需的信息。消息队列和管道类似,消息被读走了就木有了。
目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息队列,系统V消息队列目前被大量使用。系统V消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。
消息队列是IPC(进程间通信)方式之一。许多方面看来,消息队列类似于管道,但是却没有与打开与关闭管道的复杂关联。消息队列提供了一种在两个不相关的进程之间传递数据的简单高效的方法。与有名管道比较起来,消息队列的优点在独立于发送与接收进程,这减小了在打开与关闭有名管道之间同步的困难。
二,消息队列的使用方法
(1):打开或创建:进程通过调用函数msgget来创建或者获得一个消息队列。
(2):读写操作:msgrcv和msgsnd分别用于向消息队列发送和从消息队列接收数据。
(3):删除:当进程不需要消息队列时,使用msgctl通过IPC_RMID命令来删除它。
三,创建消息队列
要使用消息队列进行进程间通信首先需要创建消息队列资源。msgget函数用于创建一个新的消息队列,或是对一个已经创建的消息队列进行存取。msgget函数的具体信息见下表:
头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> | ||
函数原型 | int msgget(key_t key, int msgflg); | ||
返回值 | 成功 | 失败 | 是否设置errno |
消息队列标识符(非负整数) | -1 | 是 |
说明:msgget函数用于创建或访问消息队列,并获得消息队列标识符。msgget返回的消息队列标识符的值与参数key相关。当参数key取值为IPC_PRIVATE或是参数key不为IPC_PRIVATE,且内核中没有与key相同的消息队列,参数msgflg中设置了IPC_CREAT位,将创建新的消息队列。
如果参数msgflg设置了IPC_CREAT和IPC_EXCL位,且参数key与系统中某个消息队列的关键字相同,msgget函数将调用失败。同时,将errno设置为EEXIST。
如果参数msgflg设置了IPC_CREAT(没有同时设置IPC_EXCL位),且参数key与系统中某个消息队列的关键字相同,msgget函数调用不会失败。函数将返回已存在的消息队列的标识符。
错误信息:
EACCES:存在与参数key对应的消息队列,调用进程没有访问消息队列的权限。
EEXIST:存在与参数key对应的消息队列,参数msgflg设置了IPC_CREAT和IPC_EXCL。
ENOENT:不存在与key对应的消息队列,参数msgflg没有设置IPC_CREAT位。
ENOMEM:系统没有足够的内存保存新创建的消息队列的数据结构。
ENOSPC:超出系统容纳的最大消息队列数(超过MSGMNB)。
实例:
程序为使用msgget函数创建消息队列的实例。在调用msgget函数时,使用了不同的参数。示范在不同调用参数的情况下,msgget函数的返回结果和错误信息。下表为调用msgget函数的参数组合:
参数key | 参数msgflg | msgget调用结果 | errno信息 |
IPC_PRIVATE | 无要求 | 成功 | 无 |
不存在相同key | IPC_CREAT|权限值 | 成功 | 无 |
存在相同key | IPC_CREAT| IPC_EXCL|权限值 | 失败 | EEXIST |
存在相同key | IPC_CREAT|权限值 | 成功 | 无 |
具体代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main(void)
{
key_t key;
int msgid;
int proj_id;
key = IPC_PRIVATE;
msgid = msgget(key, 0777);
if (msgid == -1)
{
perror("cannot create message queue");
return (1);
}
else
{
printf("1. key=IPC_PRIVATE, message queue msgid = %d\n", msgid);
}
proj_id = 1;
key = ftok("./program", proj_id);
if (key == -1)
{
perror("cannot generate message queue key");
return (1);
}
msgid = msgget(key, IPC_CREAT|0777);
if (msgid == -1)
{
perror("cannot create message queue");
return (1);
}
else
{
printf("2. key=%d generated by ftok, message queue msgid= %d\n",
key, msgid);
}
msgid = msgget(key, IPC_CREAT|IPC_EXCL|0777);
if (msgid == -1)
{
perror("cannot create message queue");
}
msgid = msgget(key, IPC_CREAT|0777);
if (msgid == -1)
{
perror("cannot create message queue");
}
else
{
printf("access the existing message queue\n");
}
return (0);
}
运行结果:
[root@localhost test]# ./msgget
1. key=IPC_PRIVATE, message queue msgid = 65536
2. key=16912503 generated by ftok, message queue msgid= 98305
cannot create message queue: File exists
access the existing message queue
[root@localhost test]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x00000000 65536 root 777 0 0
0x01021077 98305 root 777 0 0
[root@localhost test]#
四,消息队列中的基本数据结构
1、msqid_ds数据结构
系统为每个消息队列维护着对应的结构体msqid_ds的实例。该结构的定义具体如下:
struct msqid_ds {
structipc_perm msg_perm;
structmsg *msg_first; /* first message on queue,unused */
structmsg *msg_last; /* last message inqueue,unused */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsignedlong msg_lcbytes; /* Reuse junk fields for 32 bit */
unsignedlong msg_lqbytes; /* ditto */
unsignedshort msg_cbytes; /* current number of bytes on queue */
unsignedshort msg_qnum; /* number of messages in queue */
unsignedshort msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
msg_perm:结构体ipc_perm的一个实例,用于保存消息队列的访问权限及消息队列的创建者等信息。结构体ipc_perm具体定义如下:
struct ipc_perm
{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode;
unsignedshort seq;
};
msg_stime:最后调用msgsnd函数的时间。
msg_rtime:最后调用msgrcv函数的时间。
msg_ctime:消息队列最后改变的时间。
msg_qnum:消息队列中消息的数目。
msg_qbytes:消息队列最大比特数。
msg_lspid:最后调用msgsnd进程的进程号。
msg_lrpid:最后调用msgrcv进程的进程号。
创建新的消息队列,将对与消息队列相关的msqid_ds结构体中的变量进行设置,具体如下:
msg_perm.cuid和mst_perm.uid将被设置成调用进程的有效用户ID(EUID)。
msg_perm.cgid和mst_perm.gid被设置成调用进程的有效组ID(EGID)。
msg_perm.mode将被设置成与msgflg相同的权限值。
msg_qnum、msg_lspid、msg_lrpid、msg_stime和msg_rtime将被设置为0.
msg_ctime被设置成系统当前时间。
msg_qbytes将会与系统规定的消息队列最大比特数(MSGMNB)相等。
2、内核msg数据结构
当有消息写入消息队列时,系统将消息加入消息队列维护的消息链表中。内核维护的结构体msg的定义如下所示:
struct msg
{
structmsg *msg_next;
long msg_type;
ushort msg_ts;
short msg_spot;
};
msg_next:指向消息队列的下一个消息的指针。
msg_type:消息类型,该消息类型是由用户自己定义的。
msg_ts:消息的长度。
msg_spot:指向消息内容的指针。
五,msgctl函数
消息队列的所有者和访问权限在消息队列创建的时候已经确定。msgctl函数用于对消息队列的这些属性进程修改。该函数的具体信息如下表:
头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> | ||
函数原型 | int msgctl(int msqid, int cmd, struct msqid_ds *buf); | ||
返回值 | 成功 | 失败 | 是否设置errno |
依赖于cmd参数 | -1 | 是 |
说明:msgctl函数用于修改消息队列的权限和所有者属性。参数msqid为要修改的消息队列的标识符。buf为指向msqid_ds结构体的指针。参数cmd可以取如下的值:
IPC_STAT:读取内核中与参数msqid相关的数据结构,将其存放到指向msqid_ds结构体的buf指针中。调用进程必须对消息队列有读取权限。
IPC_SET:将内核数据结构中与msqid_ds结构体中成员对应的数据设置为参数buf中的值。
IPC_RMID:从内核中移除消息队列,唤醒所有等待的读进程和写进程(这些进程将返回,errno将被设置为EIDRM)。调用进程必须有相应的访问权限,或其EUID与消息队列的创建者或消息队列的所有者相同。
IPC_INFO(Linux特有的参数):返回系统级别的消息队列限制,结构保存在buf指针中。该指针是指向结构体msginfo的指针。msginfo数据结构(在sys/msg.h中定义,同时必须定义_GNU_SOURCE宏)的具体定义如下:
struct msginfo {
intmsgpool; //消息队列占用的缓存大小,未被使用
intmsgmap; //消息表中最大的数目,未使用
intmsgmax; //单条消息中最大长度(单位为byte)
int msgmnb; //消息队列最大可写入的字节数,通常用于在创建消息队列时初始化msg_qbytes值
intmsgmni; //消息队列中可容纳的消息条数
intmsgssz; //消息段大小,为使用
intmsgtql; //系统中所有消息队列可容纳的消息数,未使用
unsignedshort msgseg; //最大消息段大小,未使用
};
MSG_INFO(Linux特有的参数):返回的msginfo信息与使用IPC_INFO相同。同时,将获得系统当前消息队列资源消耗情况。
msgpool:系统中存在的消息队列数。
msgmap:系统中所有消息队列的消息数。
Msgtql:系统中所有消息队列占用的总字节数。
MSG_STAT(Linux特有的参数):与使用cmd参数使用IPC_STAT的结果相同。只是msqid参数不是消息队列的标识符,而是内核的消息队列信息。
cmd参数取IPC_STAT、IPC_SET和IPC_RMID时,函数成功执行返回值为0,使用IPC_INFO或MSG_INFO时,返回内核内部最大的消息队列信息。
错误信息:
EACCESS:cmd为IPC_STAT或MSG_STAT,调用进程无读取消息队列的权限。
EFAULT:cmd为IPC_SET或IPC_STAT,buf地址空间不可访问。
EIDRM:消息队列已被删除。
EINVAL:非法cmd参数或msqid参数。
EPERM:cmd为IPC_SET或PC_RMID,调用进程无对应权限。
实例:
程序为使用msgctl获得创建的消息队列信息的实例。程序首先通过msgget函数产生消息队列,然后调用msgctl函数获得创建的消息队列的信息。
程序中使用了两种参数来调用msgctl函数:一种是IPC_STAT,该参数是POSIX标准中的参数,对应所有遵循POSIX标准的操作系统而言,使用该参数不会存在任何的问题;还有一种是Linux系统中特有的参数,为了保证程序的通用性,在代码中通过宏开关来控制是否编译使用Linux特有的参数。
由于在cmd参数中使用MSG_INFO,buffer获得的是指向msginfo结构体的指针,因此在使用前必须做强制类型转换。msginfo结构体的定义只有在定义了_GNU_SOURCE时才可见。
具体代码如下:
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/msg.h>
#define LINUX_ENV
#ifdef LINUX_ENV
#define _GNU_SOURCE
#endif
int main(void)
{
key_t key;
int proj_id;
int msqid;
struct msqid_ds buffer;
proj_id = 2;
key = ftok("./program", proj_id);
if (key == -1)
{
perror("Cannot generate the IPC key");
return (1);
}
msqid = msgget(key, IPC_CREAT|0666);
if (msqid == -1)
{
perror("Cannot create message queue resource");
return (1);
}
msgctl(msqid, IPC_STAT, &buffer);
printf("======Message Inof======\n");
printf("effective user id: %d\n", buffer.msg_perm.uid);
printf("effective group id: %d\n", buffer.msg_perm.gid);
printf("message queue's creator user id: %d\n", buffer.msg_perm.cuid);
printf("message queue's creator group id: %d\n", buffer.msg_perm.cgid);
printf("access mode: %x\n", buffer.msg_perm.mode);
printf("Maximum number of bytes allowed in message queue: %d\n",
buffer.msg_qbytes);
printf("current number of message in message in message queue: %d\n",
buffer.msg_qnum);
#ifdef LINUX_ENV
msgctl(msqid, MSG_INFO, &buffer);
struct msginfo *queue_info;
queue_info = (struct msginfo *)(&buffer);
printf("Size in bytes of buffer pool used to hold message data: %d\n",
queue_info->msgpool);
printf("Max. # of entries in message map: %d\n", queue_info->msgmap);
printf("Max. # of segments: %d\n", queue_info->msgseg);
#endif
return (0);
}
运行结果:
======Message Inof======
effective user id: 0
effective group id: 0
message queue's creator user id: 0
message queue's creator group id: 0
access mode: 1b6
Maximum number of bytes allowed in message queue: 65536
current number of message in message in message queue: 0
Size in bytes of buffer pool used to hold message data: 1
Max. # of entries in message map: 0
Max. # of segments: 16384
[root@localhost test]#