文章目录
写在前面:此系列主要参考自UNIX网络编程-卷2-进程间通信,将会有大量demo。
书籍链接:微云链接
什么是消息队列
消息队列可认为是内核维护的一个消息链表。有写权限的线程可以往队列中放置消息,有读权限的线程可以从队列中取走消息,每个消息就是一个记录,它由发送者赋予一个优先级,在某个进程往队列写入消息之前,并不需要另外的进程在那里等待(和管道不同哦)。可以说,消息队列其实是具有随内核持续性的。
消息队列中的每个消息都具有如下属性:
- 一个无符号整数优先级
- 消息的数据部分长度
- 数据本身
打开 | 关闭 | 删除 消息队列:mq_open、mq_close和mq_unlink
#include <mqueue.h>
mqd_t mq_open(const char* name,int oflag,.../*mode_t mode,struct mq_attr* attr*/);
/*
* 作用:创建(打开)一个新的(已存在的)消息队列
* 返回值:返回一个消息队列描述符,用于其他消息队列函数的第一个参数
* name:文件路径名
* oflag:O_RDONLY、O_WRONLY、O_RDWR 之一,再按位或上其他文件标志
* mode:创建新队列时所需要
* attr:给新队列指定某些属性,如果为空,则使用默认属性
*/
#include <mqueue.h>
int mq_close(mqd_t mqdes);
/*
* 作用:关闭一个已打开的消息队列文件描述符(并不删除,只是在当进程的文件表中删除)
* 返回值:成功返回0,失败返回-1
* mqdes:要关闭的消息队列描述符
*/
#include <mqueue.h>
int mq_unlink(const char* name);
/*
* 作用:在系统中删除消息队列
* 返回值:成功返回0,失败返回-1
* name:要删除的消息队列路径
*/
每个消息队列都有一个保存其当前打开文件描述符的引用计数(文件、智能指针),但是mq_unlink在引用计数仍大于0就可以删除消息队列。值得注意的是,要到最后一个mq_close发生了才进行析构。
一个简单的demo:
#include <mqueue.h>
#include <unistd.h>
#include <sys/stat.h>
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
int flags = O_RDWR | O_CREAT;
int choose = -1;
//处理输入参数
while ((choose = getopt(argc, argv, "e")) != -1)
{
switch (choose)
{
case 'e':
flags |= O_EXCL;
break;
default:
break;
}
}
//optget 会在optind中存放下一个待处理参数的下标
if (optind != argc - 1)
{
cout << "error" << endl;
exit(0);
}
//s_irusr|s_iwusr 设定访问权限
mqd_t mqd = mq_open(argv[optind], flags, nullptr, nullptr);
if (mqd == -1)
{
cout << "error: " << errno << endl;
exit(0);
}
cout << "open file: " << mqd << endl;
mq_close(mqd);
return 0;
}
输出:
ubuntu@VM-0-2-ubuntu:~/code/WorkSpace/bin$ ./test1 /test.queue
open file: 3
注意:
1.这里的路径必须指定为/xxxx.xxx
2.需要连接库 -lrt
可以用以下代码去关闭一个打开的消息队列:
if (mq_unlink(argv[1]) == -1)
{
cout << "error: " << errno << endl;
}
获取 | 查看 属性:mq_getattr和mq_setattr
每个消息队列有四个属性,mq_getattr返回所有这些属性,mq_setattr则设置其中某个属性。
#include <mqueue.h>
int mq_getattr(mqd_t mqdes,struct mq_attr* attr);
/*
* 作用:获得这些属性
* 返回值:成功返回0,失败返回-1
* mqdes:消息队列文件描述符
* attr:返回的信息存储在这个结构体之中
*/
int mq_setaddr(mqd_t mqdes,const struct mq_attr* attr,struct mq_attr* oattr);
/*
* 作用:修改属性
* 返回值:成功返回0,失败返回-1
* attr:输入的属性
* oattr:修改完之后输出的属性
*/
struct mq_attr
{
long mq_flags;
long mq_maxmsg;
long mq_msgsize;
long mq_curmsgs;
}
指向某个mq_attr结构的指针可作为mq_open的第四个参数传递,从而允许我们在该函数的实践操作是创建一个新队列时,给它指定maxmsg和msgsize属性。
一个mq_getattr的例子:
#include <mqueue.h>
#include <unistd.h>
#include <sys/stat.h>
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
if (argc != 2)
{
cout << "less input" << endl;
exit(0);
}
mqd_t mqd = mq_open(argv[1], O_RDONLY);
if (mqd == -1)
{
cout << "error" << endl;
exit(0);
}
struct mq_attr attr;
if (mq_getattr(mqd, &attr) != -1)
{
cout << "maxmsg: " << attr.mq_maxmsg << endl;
cout << "msgsize: " << attr.mq_msgsize << endl;
cout << "curmsgs: " << attr.mq_curmsgs << endl;
}
mq_close(mqd);
return 0;
}
输出:
ubuntu@VM-0-2-ubuntu:~/code/WorkSpace/bin$ ./test1 /test.queue
maxmsg: 10
msgsize: 8192
curmsgs: 0
消息发送和接受函数:mq_send和mq_receive
这两个函数分别用于往一个队列中放置一个消息和从一个队列中取走一个消息。每个消息有一个优先级,它是一个小于MQ_PRIO_MAX
的无符号整数。
#include <mqueue.h>
int mq_send(mqd_t mqdes,const char* ptr,size_t len,unsigned int prio);
/*
* 作用:往消息队列发送一个消息
* 返回值:成功返回0,失败返回-1
* mqdes:消息队列描述符
* ptr:指向发送消息的指针
* len:发送消息的长度
* prio:发送消息的优先级
*/
ssize_t mq_receive(mqd_t mqdes,char* ptr,size_t len,unsigned int *priop);
/*
* 作用:从消息队列中取出一个消息,总是会取出优先级最高的,否则就是发布时间最早的
* 返回值:成功返回消息中的字节数,出错返回-1
* mqdes:消息队列描述符
* ptr:指向发送消息的指针
* len:发送消息的长度
* priop:消息的优先级
*/
一个简单的demo:
Server端:
#include <mqueue.h>
#include <unistd.h>
#include <sys/stat.h>
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
if (argc != 2)
{
cout << "less input" << endl;
exit(0);
}
mqd_t mqd = mq_open(argv[1], O_WRONLY);
if (mqd == -1)
{
cout << "error" << endl;
exit(0);
}
string input;
int prio = 2;
do
{
cin >> input;
if (mq_send(mqd, input.c_str(), input.size(), prio++) == -1)
{
cout << "error" << endl;
exit(0);
}
} while (input != "quit");
mq_close(mqd);
return 0;
}
客户端:
#include <mqueue.h>
#include <unistd.h>
#include <cstring>
#include <sys/stat.h>
#include <iostream>
using namespace std;
#define OUTPUT_SIZE 10000
int main(int argc, char **argv)
{
if (argc != 2)
{
cout << "less input" << endl;
exit(0);
}
mqd_t mqd = mq_open(argv[1], O_RDWR);
if (mqd == -1)
{
cout << "error" << endl;
exit(0);
}
char output[OUTPUT_SIZE] = {0};
int size = 0;
unsigned int oproi = 0;
do
{
memset(output, 0, OUTPUT_SIZE);
size = mq_receive(mqd, output, OUTPUT_SIZE, &oproi);
if (size <= 0)
{
cout << errno << " " << size << endl;
exit(0);
}
cout << "read: " << output << " proi: " << oproi << endl;
} while (size > 0);
mq_close(mqd);
return 0;
}
服务端输入:
这里是分两次输入,第一次是在启动客户端之前,第二次是在启动客户端时。
ubuntu@VM-0-2-ubuntu:~/code/WorkSpace/bin$ ./server /test.queue
hello
world
quit
ubuntu@VM-0-2-ubuntu:~/code/WorkSpace/bin$ ./server /test.queue
c++ is the best!
quit
客户端输出:
ubuntu@VM-0-2-ubuntu:~/code/WorkSpace/bin$ ./client /test.queue
read: quit proi: 4
read: world proi: 3
read: hello proi: 2
read: c++ proi: 2
read: best! proi: 5
read: the proi: 4
read: is proi: 3
read: quit proi: 6
注意:
1.mq_receive的outputbuffer
的长度必须大于mq_msgsize,也就是刚刚测的8192。
2.mq_receive一直是出于阻塞状态运行的
3.当队列中消息数达到队列上限时,是不可以添加消息的
消息队列的限制
除了我们之前提到过的mq_maxmsg
和mq_msgsize
之外,我们还可以定义另外两种限制。
- MQ_OPEN_MAX: 一个进程能够同时拥有打开着的消息队列的最大数目(posix要求最小是8)
- MQ_PRIO_MAX: 任意消息的最大优先级值+1(posix要求最小为32)
这两个常值往往定义在<unistd.h>头文件中,可以通过sysconf来获取。
一个简单的demo:
#include <unistd.h>
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
cout << "mq_open_max: " << sysconf(_SC_MQ_OPEN_MAX) << endl;
cout << "mq_prio_max: " << sysconf(_SC_MQ_PRIO_MAX) << endl;
}
输出:
ubuntu@VM-0-2-ubuntu:~/code/WorkSpace/bin$ ./sysconf
mq_open_max: -1
mq_prio_max: 32768
可以看到啊,这mq_open_max的值为-1,很有趣。根据博主这里百度到的资料:
这里主观推测来说应该是没有限制。
消息队列通知函数:mq_notify
之前根据我们对mq_receive的实验来说,这个其实是阻塞的。如果我们给消息队列描述符指定IPC_NOWAIT
,虽然说是不阻塞了,但是必须持续调用该函数以确定何时有一个消息到达,满浪费CPU资源的。
所以说我们需要一种异步事件通知的机制以告知何时有一个消息放置到了某个空消息队列。这种通知方式有两种 方式可供选择:
- 产生一个信号
- 创建一个线程来执行指定的函数
#include <mqueue.h>
int mq_notify(mqd_t mqdes,const struct sigevent* notification);
/*
* 作用:通知一个空消息队列,有数据来到
* 返回值:成功返回0,失败返回-1
* mqdes:消息队列描述符
* notification:如果其非空,那么当前进程希望在有一个消息到达队列时得到通知
* 如果为空,且当前进程目前被注册为接受所指定队列的通知
*/
一个简单的demo:
服务端代码不用改,只用改一下客户端代码就ok。
#include <mqueue.h>
#include <unistd.h>
#include <cstring>
#include <sys/stat.h>
#include <signal.h>
#include <iostream>
using namespace std;
#define OUTPUT_SIZE 10000
mqd_t mqd;
char output[OUTPUT_SIZE] = {0};
struct sigevent sigev;
//从mqd中读取一个消息
static void sig_urs1(int signo)
{
ssize_t size = 0;
mq_notify(mqd, &sigev);
size = mq_receive(mqd, output, OUTPUT_SIZE, nullptr);
cout << "read: " << output << endl;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
cout << "less input" << endl;
exit(0);
}
mqd = mq_open(argv[1], O_RDWR);
if (mqd == -1)
{
cout << "error" << endl;
exit(0);
}
//当有SIGUSR1来到时,调用sigurs1
signal(SIGUSR1, sig_urs1);
sigev.sigev_notify = SIGEV_SIGNAL;
sigev.sigev_signo = SIGUSR1;
mq_notify(mqd, &sigev);
for (;;)
{
sleep(2);
cout << "do other things" << endl;
}
mq_close(mqd);
return 0;
}
服务端输入:
ubuntu@VM-0-2-ubuntu:~/code/WorkSpace/bin$ ./server /test.queue
hello
world
quit
客户端输出:
ubuntu@VM-0-2-ubuntu:~/code/WorkSpace/bin$ ./client /test.queue
do other things
read: hello
do other things
do other things
read: world
do other things
do other things
do other things
read: quitd
do other things
do other things
注意:
- mq_notify每次在mq_receive读出数据之后都需要重新设置。
- 无论是什么信号,都可以唤醒sig_usr函数
参考文献
[1]UNIX网络编程_卷2_进程间通信 第五章 posix消息队列