目录
1. Posix 消息队列简介
Posix 消息队列是基于文件描述符的,因此可以使用诸如select、poll和epoll等IO多路复用方式,这应该是最大的优点,此外,它还是一个优先级队列,优先级最高的消息会先出队列。从我的开发经验来看,实际项目中并不推荐使用系统提供的消息队列,主要原因是限制太多,比如说Posix 消息队列,系统范围内能够创建的消息队列个数是优先的,此外,消息的大小和消息队列的容量也受限,还有一点是仅支持优先级,不支持FIFO,这一点SystemV消息队列是支持的。虽然不怎么使用,但是还是需要学习一下它的基本用法,这样后续需要自己设计的时候可以参考下。
Posix mq可以使用mq_open来创建或者获取,该函数返回消息队列描述符mqd_t,每个消息队列都有一个名字,以斜杠'/'开头,长度不超过NAME_MAX,且后面不能再有斜杠。不同的进程可以通过同一个名字来操作同一个消息队列。消息通过mq_send和mq_receive来接收和发送,当不需要消息队列的时候,使用mq_close关闭它,关闭不代表删除,消息队列具有系统生存期,可以使用mq_unlink来删除它。对了,Posix mq还提供一个异步接收消息的特性,使用mq_notify函数,该接口向消息队列注册或注销一个通知,希望有消息的时候能够通知它,通知方式有两种,一种是通过信号,一种是通过一个线程。这里限制比较多,首先,消息队列只能注册一个通知,其次,通知是一次性的,通知完如果还想继续通知需要重新注册,还有一点是如果消息被其它进程通过mq_receive取到了,通知不会发生。我感觉这是一个非常鸡肋的功能,O(∩_∩)O哈哈~
下面介绍下相关的接口和使用例子
2. API接口
2.1 创建或打开消息队列
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <mqueue.h>
/**
* @brief 创建或获取消息队列
*
* @params name 消息队列关联的名字,斜杠开头,长度不超过NAME_MAX(e.g. 255)
* @params oflag 标志位,可以O_RDONLY| O_WRONLY| O_RDWR| O_CLOEXEC| O_CREAT| O_EXCL| O_NONBLOCK
* 这里如果指定了O_CREAT标志位,还要填写额外两个参数,mode和attr
*
* @params mode,参考open函数,通常填0即可
* @params attr,设置一些属性
* struct mq_attr {
* long mq_flags; /* 不用填 */
* long mq_maxmsg; /* Max. # 消息队列中消息最大个数 */
* long mq_msgsize; /* Max. message size (bytes) 最大容量 */
* long mq_curmsgs; /* 不用填写 */
* };
* @returns 成功返回描述符,失败返回-1
*/
mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode,
struct mq_attr *attr);
# 需要加上编译选项-lrt
* Link with -lrt.
2.2 发送消息
#include <mqueue.h>
/**
* @brief 发送消息,如果队列满了并且没有设置NONBLOCK标志位会阻塞
*
* @params mqdes 消息队列描述符
* @params msg_ptr 要发送的消息buffer
* @params msg_len 消息大小
* @params msg_prio 消息优先级
*
* @returns 成功返回0,失败返回-1
**/
int mq_send(mqd_t mqdes, const char *msg_ptr,
size_t msg_len, unsigned int msg_prio);
#include <time.h>
#include <mqueue.h>
/**
* @brief 同上,多了一个时间参数,用于控制在队列满了的情况下,等待多长时间
*
* @params abs_timeout 时间
* @returns 成功返回0,失败返回-1
**/
int mq_timedsend(mqd_t mqdes, const char *msg_ptr,
size_t msg_len, unsigned int msg_prio,
const struct timespec *abs_timeout);
# 编辑要加上 -lrt选项
Link with -lrt.
2.3 接收消息
#include <mqueue.h>
/**
* @brief 接收消息,无消息时默认阻塞,可以设置不阻塞标志位更改
*
* @params mqdes 消息队列描述符
* @params msg_ptr 消息缓存buffer
* @params msg_len 消息缓存大小,必须大于mq_msgsize,这个值可以通过mq_getattr接口获得
* @params msg_prio 如果设置了则返回消息的优先级
* @returns 成功返回消息大小,失败返回-1
**/
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr,
size_t msg_len, unsigned int *msg_prio);
#include <time.h>
#include <mqueue.h>
/**
* @brief 功能同上,只不过多了一个阻塞时间
**/
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,
size_t msg_len, unsigned int *msg_prio,
const struct timespec *abs_timeout);
2.4 获取、设置消息队列属性
#include <mqueue.h>
/**
* @brief 获取消息队列属性
*
* @params mqdes 消息队列描述符
* @params attr 属性buffer
struct mq_attr {
long mq_flags; /* Flags: 0 or O_NONBLOCK */
long mq_maxmsg; /* Max. 最大消息个数 */
long mq_msgsize; /* Max. 消息队列最大容量 */
long mq_curmsgs; /* 当前队列消息数 */
};
* @returns 成功返回0,失败返回-1
**/
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
/**
* @brief 设置消息队列属性
*
* @params mqdes 消息队列描述符
* @params attr 属性buffer, 这里实际上只能设置attr里面的mq_flags标志位
* @returns 成功返回0,失败返回-1
**/
int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr,
struct mq_attr *oldattr);
# 编译链接选项 -lrt
Link with -lrt.
2.5 关闭消息队列
#include <mqueue.h>
/**
* @brief 关闭消息队列,并不实际删除消息队列
*
* @params mqdes 消息队列描述符
* @returns 成功返回0,失败返回-1
**/
int mq_close(mqd_t mqdes);
2.6 删除消息队列
#include <mqueue.h>
/**
* @brief 将消息队列从系统中删除,实际的销毁要等到所有进程关闭消息队列描述符才发生
*
* @params name 消息队列名字
* @returns 成功返回0,失败返回-1
**/
int mq_unlink(const char *name);
2.7 注册消息通知
#include <mqueue.h>
/**
* @brief 注册或注销一个消息通知,当收到消息的时候根据指定的方式进行回调,可以是信号也可以执行一个线程。仅执行一次,需要反复通知则每次都要重新注册,最多允许一个进程注册。只有消息队列从空变为非空并且没有其它进程等待消息的时候,才触发通知。
*
* @params mqdes 消息队列名字
* @params sevp, 为空表示注销,非空指定回调方式,或是信号或者是线程
* @returns 成功返回0,失败返回-1
**/
int mq_notify(mqd_t mqdes, const struct sigevent *sevp);
3. 示例
3.1 消息生产者
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <mqueue.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define MQ_NAME "/mq_0"
struct smessage{
char name[128];
char content[1024];
};
int main(int argc, char** argv)
{
int ret = 0;
if (argc < 4)
{
printf("Usage: ./mq_write name message prio\n");
return 0;
}
mqd_t mq_fd;
// 打开或创建消息队列,如果指定C_CREAT标志位则需要提供额外两个参数
mq_fd = mq_open(MQ_NAME, O_RDWR|O_CREAT, 0, NULL);
if (mq_fd == (mqd_t)-1)
{
perror(__FUNCTION__);
return -1;
}
struct smessage msg;
memset(&msg, 0, sizeof(msg));
strncpy(msg.name, argv[1], strlen(argv[1]));
strncpy(msg.content, argv[2], strlen(argv[2]));
unsigned int prio = (unsigned int)atoi(argv[3]);
// 发送消息
ret = mq_send(mq_fd, (const char *)&msg, sizeof(msg), prio);
if (ret == -1)
{
perror("mq_send error");
return ret;
}
printf("mq_send succ\n");
return 0;
}
3.2 消息消费者
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <mqueue.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
// 消息队列名字
#define MQ_NAME "/mq_0"
struct smessage{
char name[128];
char content[1024];
};
int main()
{
mqd_t mq_fd;
// 打开或创建消息队列,如果指定O_CREAT,则需要填充额外两个参数
mq_fd = mq_open(MQ_NAME, O_RDWR|O_CREAT, 0, NULL);
if (mq_fd == (mqd_t)-1)
{
perror(__FUNCTION__);
return -1;
}
// 获取消息队列属性,读取消息的时候要用到
struct mq_attr attr;
if (mq_getattr(mq_fd, &attr) == -1)
{
perror("mq_getattr");
return -1;
}
printf("mq_msgsize:%ld\n", attr.mq_msgsize);
// 分配消息缓存
char *buffer = malloc(attr.mq_msgsize);
if (buffer == NULL)
{
printf("malloc error\n");
return -1;
}
memset(buffer, 0, attr.mq_msgsize);
struct smessage *msg;
unsigned int prio = 0;
// 读取消息
if (mq_receive(mq_fd, buffer,attr.mq_msgsize, &prio) != -1)
{
msg = (struct smessage*)buffer;
printf("recv msg, name:%s, content:%s, prio:%lu\n", msg->name, msg->content, prio);
}
else
{
perror("mq_receive error");
}
// 释放buffer
free(buffer);
// 关闭消息队列
mq_close(mq_fd);
return 0;
}
3.3 编译运行
# Makefile
default:
gcc -o mq_read mq_read.c -lrt
gcc -o mq_write mq_write.c -lrt
clean:
rm -rf mq_read mq_write
生产三条优先级不同的消息,可以看到读取的时候是按照优先级来读取的。
4. 通过文件系统操作消息队列
# mkdir /dev/mqueue
# mount -t mqueue none /dev/mqueue