消息队列
消息队列是一种以链表为结构组织的数据,存放在Linux内核中,是由各进程通过消息队列标识符来引用的一种数据传送方式。每个消息队列都有一个队列头,利用结构struct msg_queue来描述。队列头中包含了该消息队列的基本信息,包括消息队列键值、用户ID、组ID、消息数目等,甚至记录了最近对消息队列读写进程的PID。
消息队列的队列头存在于内核空间中,结构定义如下:
struct msg_queue
{
struct ipc_perm q_perm;
time_t q_stime; //上一条消息的发送时间
time_t q_rtime; //上一条消息的接收时间
time_t q_ctime; //上一次修改时间
unsigned long q_cbytes; //当前队列中的字节数据
unsigned long q_qnum; //队列中的消息数量
unsigned long q_qbytes; //队列的最大字节数
pid_t q_lspid; //上一条发送消息的PID
pid_t q_lrpid; //上一条接收消息的PID
struct list_head q_messages;
struct list_head q_receivers;
struct list_head q_senders;
};
结构msqid_ds用来设置或返回消息队列的信息,存在于用户空间中,结构定义如下:
struct msqid_ds
{
struct ipc_perm msg_perm;
struct msg *msg_first; //队列中的第一条消息
struct mag *msg_last; //队列中的最后一条消息
time_t q_stime; //上一条消息的发送时间
time_t q_rtime; //上一条消息的接收时间
time_t q_ctime; //上一次修改时间
unsigned long q_cbytes; //当前队列中的字节数据
unsigned long q_qnum; //队列中的消息数量
unsigned long q_qbytes; //队列的最大字节数
pid_t q_lspid; //上一条发送消息的PID
pid_t q_lrpid; //上一条接收消息的PID
};
消息的结构体为ipc_perm,其结构定义如下:
struct ipc_perm
{
//内核中用于记录消息队列的全局数据结构msg_ids能够访问到该结构
key_t key; //该键值唯一对应一个消息队列
uid_t uid; //所有者的有效用户ID
gid_t gid; //所有者的有效组ID
uid_t cuid; //创建者的有效用户ID
gid_t cgid; //创建者的有效组ID
mode_t mode; //此对象的访问权限
unsigned long seq; //对象的序号
};
消息队列的操作
创建或打开
Linux内核提供了msgget函数用于创建或者打开一个消息队列,其标准调用格式如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
参数msgflg说明如下:
·当参数key的取值为IPC_PRIVATE时,不管msgflg为何值,函数都将创建一个新的消息队列。
·当参数key的取值不为IPC_PRIVATE时,操作类型就取决于msgflg的值。如果msgflg中设置了IPC_CREATE位而没有设置IPC_EXCL位,则既可能执行打开操作也可能执行创建操作,这取决于内核的消息队列中是否存在相同的key值。
·当参数key的取值不是IPC_PRIVATE时,如果msgflg中同时设置了IPC_CREATE和IPC_EXCL,则只会执行创建消息队列的操作:当key的取值不与存在的任何一个消息队列ID相同时就会执行创建队列操作,否则函数就会出错返回。
因此要执行打开消息队列的操作只有一种可能:参数key不为IPC_PRIVATE,且参数msgflg不能设置IPC_EXCL。
#include <iostream>
#include <cstdlib>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
if (argc < 2)
{
std::cerr << "参数错误\n";
exit(1);
}
key_t key = ftok(*(argv+1), 1); //ftok函数生成队列键值
if (key < 0)
{
std::cerr << "获取消息队列键值失败\n";
exit(1);
}
int qid = msgget(key, IPC_CREATE|0666); //打开或创建队列
if (qid < 0)
{
std::cerr << "创建消息队列出错\n";
exit(1);
}
else
{
std::cout << "创建消息队列成功\n";
}
return 0;
}
消息队列控制
函数msgctl可以对消息队列进行以下操作:
·查看消息队列相连的数据结构
·改变消息队列的许可权限
·改变消息队列的拥有者
·改变消息队列的字节大小
·删除一个消息队列
其标准调用格式如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgqid, int cmd, struct msqid_ds *buf);
/*
*参数msqid为消息队列ID(msgget返回的值)
*参数cmd为指定要求的操作
*/
其中参数cmd的取值说明如下:
值 | 操作 |
---|---|
IPC_STAT | 将msqid指定的消息队列的内核控制数据结构msqid_ds复制到buf指定的用户空间中 |
IPC_SET | 设置msqid指定的消息队列的有效用户与组ID、操作权限以及消息队列的字节数,即设置msqid相连的数据结构msg_perm的各成员uid、gid、mode和qbytes的值为buf所指结构中给出的值 |
IPC_RMID | 删除msqid以及它指向的消息队列、相连的数据结构 |
执行IPC_STAT命令的进程必须具有消息队列的读权限,执行IPC_SET和IPC_RMID命令的进程只能是消息队列的创建者、拥有者或是特权级相符的进程。此外,只有特权进程才能增大消息队列的字节数。
消息队列的发送和接收
Linux提供了msgsnd函数用于消息队列的发送,接收则为msgrcv。标准调用格式如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
int msgrcv(int msqid, const void *msgp, size_t msgsz, long msgtyp, int msgflg);