一、消息队列的概念
消息队列:内核地址空间中的内部链表,通过linux内核在各个进程之间传递内容。
消息顺序地发送到消息队列中,并以几种不同的方式从队列中获取;消息队列间通过IPC的标识符进行区别;不同的消息队列相互独立;每个消息队列中的消息构成一个独立的链表。
二、相关数据结构
定义位置:<linux/msg.h>
stuct msgbuf{
long mtype; //用正数表示的消息类型
char mtext[1]; //消息数据
};
消息大小限制:<Linux/msg.h> #define MSGMAX 8192
MSGMAX:mtype + mtext
msgid_ds结构的IPC对象分为3类,消息队列对应的类型结构如下:
定义位置:<Linux/msg.h>
struct msqid_ds
{
struct ipc_perm msg_perm;
time_t msg_stime; //发送到队列的最后一个消息的时间戳
time_t msg_rtime; //从队列中获取的最后一个消息的时间戳
time_t msgg_ctime; //对队列进行最后一次变动的时间戳
unsigned long __msg_cbytes; //在队列上所驻留的字节总数
msgqnum_t msg_qnum; //当前处于队列中的消息数目
msglen_t msg_qbytes; //队列中能容纳的字节的最大数目
pid_t msg_lspid; //发送最后一个消息进程的PID
pid_t msg_lrpid; //接收最后一个消息进程的PID
};
ipc_perm:内核IPC对象的许可权限的存储结构。
定义位置:<linux/ipc.h>
struct ipc_perm
{
key_t key; //函数msgget()使用的键值
uid_t uid; //用户的UID
gid_t gid; //用户的GID
uid_t cuid; //创建者的UID
gid_t cgid; //创建者的GID
unsigned short mode; //权限
unsigned short seq; //序列号
};
三、相关函数
函数原型 | key_t ftok(const char *pathname, int proj_id) |
---|---|
头文件 | <sys/types.h><sys/ipc.h> |
参数 | 说明 |
pathname | 生成key所依据的路径 |
proj_id | 生成key所依据的id值 |
返回值 | 成功:key值,失败:-1 |
描述:
ftok()接口使用文件(必须为已存在且可访问的文件)标识与proj_id(不能为0)的低八位生成系统IPC键值,适用于msgget(),semget()或shmget()。当pathname与proj_id相同时,返回的结果总是相同的。当pathname或proj_id任一参数不同时,返回值应该是不同的。(不能确保不同)
在libc4和libc5下该函数的原型为:
key_t ftok(char *pathname, char proj_id)。现在,虽然proj_id是int型的,但是仍然仅有8位被使用,典型的使用方法会用一个ASCII的字符来为proj_id赋值,这也就是为何proj_id值为0被视为未定义的行为。
此接口并不保证结果唯一,最好的一种解决方案为:将proj_id的字节、inode的低16位与设备编号的低8位结合起来放入一个32位的结果当中。
函数原型 | int msgget(key_t key, int msgflg) |
---|---|
头文件 | <sys/types.h><sys/ipc.h><sys/msg.h> |
参数 | 说明 |
key | 消息队列的键值 |
msgflg | 操作key对应的动作 |
返回值 | 成功:消息队列标识符,失败:-1 |
描述:
msgget()系统调用返回与key相关联的消息队列标识符。用于创建或访问一个消息队列。若key有值IPC_PRIVATE。若消息队列不存在,则IPC_CREAT在msgflg中被指定。
若IPC_CREAT和IPC_EXCL同时在msgflg中被指定,且对应的消息队列已存在,则该调用出错。
在创建的时候,msgflg的低位定义了消息队列的权限。该权限的格式与open()接口的mode中权限格式相同。
当新的消息队列被创建时,msqid_ds结构被如下初始化:
msg_perm.cunid 和 msg_perm.uid:调用进程的用户ID
msg_perm.cgid 和 msg_perm.gid:调用进程的用户组ID
msg_perm.mode的低9位:msgflg的低九位
msg_qnum,msg_lspid,msg_lrpid,msg_stime 和 msg_rtime被置为0
msg_ctime被置为当前时间
msg_qbytes被置为系统限制值:MSGMNB
函数原型 | int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg) |
---|---|
头文件 | <sys/types.h><sys/ipc.h><sys/msg.h> |
参数 | 说明 |
msqid | 消息队列id |
msgp | 消息结构内存首地址 |
msgsz | 消息的大小 |
msgflg | 调用标志 |
返回值 | 成功:0,失败:-1 |
msgflg:
IPC_NOWAIT:若队列空间充足,则随即执行并返回。否则,执行失败并返回EAGAIN。若不指定该选项,则在队列空间不足时,执行阻塞至空间充足后继续执行。
函数原型 | ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg) |
---|---|
头文件 | <sys/types.h><sys/ipc.h><sys/msg.h> |
参数 | 说明 |
msqid | 消息队列id |
msgp | 存储消息的缓存首地址 |
msgsz | 指定缓冲区msgp最大字节数 |
msgtyp | 指定消息类型 |
msgflg | 指定附加动作 |
返回值 | 成功:拷贝到队列中mtext中的字节数,失败:-1 |
msgsz:
指定存储消息的缓冲区最大字节数,当取出的消息大小大于此值时,若在msgflg中指定MSG_NOERROR,则消息内容将被截断,截断的数据将被丢弃。若未指定MSG_NOERROR,则消息将不会被移除,调用失败并返回-1,同时错误码errno被置为E2BIG。
msgtyp:
0:读取队列中第一条消息;
大于0:读取队列中第一条类型为该值得消息。若msgflg中指定MSG_EXCEPT,则读取队列中第一条类型号不为该值得消息;
小于0:读取队列中第一条类型值小于或等于该值得绝对值的最小类型消息。
msgflg:
位掩码,由0或以下标志进行或(|)运算构成:
IPC_NOWAIT:若无消息则立即返回,并设错误码为ENOMSG。
MSG_EXCEPT:在msgtyp参数大于0下使用,读取队列中第一条类型与msgtyp不同的消息。
MSG_NOERROR:在消息内容长度大于msgsz时对消息进行截断。
当队列中没有请求的类型的消息时,且msgflg中未指定IPC_NOWAIT,调用进程将阻塞至下列任一情况发生:
一条所需类型的消息被放入队列;
消息队列被移除,此情况下,系统调用失败,并置错误码为EIDRM.
调用进程捕获到信号。此情况下,系统调用失败并置错误码为EINTR.
|函数原型|int msgctl(int msqid, int cmd, struct msqid_ds *buf)|
头文件 | <sys/types.h><sys/ipc.h><sys/msg.h> |
---|---|
参数 | 说明 |
msqid | 消息队列id |
cmd | 对队列的操作 |
buf | 结果存储缓冲区 |
返回值 | 成功:IPC_STAT,IPC_SET,IPC_RMID返回0,失败:-1 |
msgctl()通过cmd中指定的操作对消息队列进行控制。
cmd:
IPC_STAT:在调用者对消息队列有读取权限的前提下,将内核中与指定队列id相关的数据结构拷贝至buf中。
IPC_SET:将buf指向的msqid_ds结构找中的一些成员拷贝到内核与消息队列对应的数据结构中。结构中被更新的成员:msg_ctime,msg_qbytes,msg_perm.uid,msg_perm.gid,msg_perm.mode。调用进程的有效UID必须与msg_perm.uid或msg_perm.cuid相匹配,或者该用户拥有对应的特权。在将msg_qbytes提升至超过MSGMNB的值得时候,需要对应的特殊权限。
IPC_RMID:移除消息队列,并唤醒所有等待中的读/写进程。主调进程需要对应的特权,或者是队列的拥有者或者创建者。
四、代码简例
#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
struct my_msg
{
long mtype;
char mtext[512];
int num;
};
int main()
{
key_t msg_key = ftok(".", 'a');
//generate message queue key
if(-1 == msg_key)
{
std::cout << __LINE__;
perror("Create msg_key:");
return -1;
}
//create message queue
int msg_qid = msgget(msg_key, IPC_CREAT);
if(-1 == msg_qid)
{
std::cout << __LINE__;
perror("Create message queue:");
return -1;
}
pid_t pid = fork();
if(-1 == pid)
{
std::cout << __LINE__;
perror("Fork:");
return -1;
}
if(0 < pid)
{
//父进程进行读取操作
ssize_t nbytes = 0;
char buf[1024];
while(true)
{
nbytes = msgrcv(msg_qid, buf, sizeof(buf), 0, 0);
if(-1 == nbytes)
{
std::cout << __LINE__;
perror("Msgrcv:");
}
else
{
struct my_msg *msg = (struct my_msg*)buf;
std::cout<< "msg type: " << msg->mtype << " metxt: " << msg->mtext << std::endl;
if(5 == msg->mtype)
{
msgctl(msg_qid, IPC_RMID, NULL);
std::cout << "Rove the message queue & exit" << std::endl;
return 0;
}
}
}
}
else
{
//子进程进行写入操作
struct my_msg msg;
int cnt = 1;
for(int i = 1; i < 6; i ++)
{
msg.mtype = i;
msg.num = cnt++;
strcpy(msg.mtext, "My self defined msg queue.");
int res = msgsnd(msg_qid, (void*)(&msg), sizeof(msg), 0);
if(-1 == res)
{
std::cout << __LINE__;
perror("Msg send:");
}
else
{
std::cout << "-----------send: " << msg.mtext << std::endl;
}
}
}
}
执行结果:
[pig@localhost code]$ g++ msgque.cpp
[pig@localhost code]$ sudo ./a.out
-----------send: My self defined msg queue.
-----------send: My self defined msg queue.
-----------send: My self defined msg queue.
-----------send: My self defined msg queue.
-----------send: My self defined msg queue.
msg type: 1 metxt: My self defined msg queue.
msg type: 2 metxt: My self defined msg queue.
msg type: 3 metxt: My self defined msg queue.
msg type: 4 metxt: My self defined msg queue.
msg type: 5 metxt: My self defined msg queue.
Rove the message queue & exit
[pig@localhost code]$