消息队列的创建
- 命名只能是/queuename.
- 默认存放在/dev/mqueue里,(posixIPC都在这里,System V 可以用ipcs查看)
- 与内核相同的持续时间——除非删除队列,否则里面的消息不丢失
- 存在引用计数,最后一个计数为0且unlink可以删除
创建:
/**
* @file 1-mqcreate.c
* 创建一个消息队列
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
// 注意Posxi消息队列的命名只能是/queuename.
// 不可以用路径,默认都是放在/dev/mqueue里
#define MQNAME "/temp.1234"
// 创建的文件的权限
#define MODE (S_IRWXU | S_IRWXG | S_IRWXO)
int main(int argc, char **argv)
{
mqd_t mqd;
mqd = mq_open(MQNAME, O_RDWR | O_CREAT, MODE, NULL);
if (mqd == (mqd_t)-1)
{
perror("mq_open");
exit(-1);
}
mq_close(mqd);
// 不加这句,temp.1234 不会被删除,这了这句话,就会被删除
// mq_unlink(MQNAME);
exit(0);
}
设置和获取队列属性
设置属性:
/**
* @file 2-mqsetattr.c
* 设置或修改消息队列的属性
* 属性有四项:flag, maxmsg, msgsize, curmsgs;
* 其中:
* 1.消息队列的最大容量和每个消息的最大长度只能在创建的时候设置,手动检查是否为0,不能超过规定值(文件中)
* 2.flag只有是否阻塞两个选项,可以随意随时设置
* 3.当前消息队列的消息的个数,任何时候不能设置,只能获取
*/
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MQNAME "/temp.1"
#define MODE (S_IRWXU | S_IRWXG | S_IRWXO)
int main(int argc, char **agrv)
{
if (argc < 2)
{
fprintf(stderr, "Using...\n");
exit(0);
}
mqd_t mqd;
struct mq_attr attr;
memset(&attr, 0, sizeof(attr));
attr.mq_maxmsg = atol(agrv[1]);
attr.mq_msgsize = atol(agrv[2]);
mqd = mq_open(MQNAME, O_RDWR | O_CREAT | O_EXCL, MODE, &attr);
if (mqd == (mqd_t)-1)
{
perror("mq_open");
exit(-1);
}
mq_close(mqd);
exit(0);
}
获取属性:
/**
* @file 2-mqgetattr.c
* 获取消息队列的属性
* 属性有四项:flag, maxmsg, msgsize, curmsgs;
* 其中:
* 1.消息队列的最大容量和每个消息的最大长度只能在创建的时候设置,手动检查是否为0
* 2.flag只有是否阻塞两个选项,可以随意随时设置
* 3.当前消息队列的消息的个数,任何时候不能设置,只能获取
*/
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#define MQNAME "/temp.1"
#define MODE (S_IRWXU | S_IRWXG | S_IRWXO)
int main(int argc, char **agrv)
{
mqd_t mqd;
struct mq_attr attr;
mqd = mq_open(MQNAME, O_RDWR, MODE, NULL);
mq_getattr(mqd, &attr);
printf("flag:%ld\tmaxmsg:%ld\tmsgsize:%ld\tcurmsgs:%ld\n", attr.mq_flags, attr.mq_maxmsg, attr.mq_msgsize, attr.mq_curmsgs);
mq_close(mqd);
exit(0);
}
取消息和推送消息
推送消息:
/**
* @file mqsend.c
* 往消息队列上发送消息,消息有优先级
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string.h>
#define MQNAME "/temp.1234"
#define MSG1 "this is a msg, prio = 10."
#define MSG2 "this is a msg, prio = 20."
int main(int argc, char **argv)
{
char *buf;
size_t buf_len = 1024;
unsigned int prio1 = 10;
unsigned int prio2 = 20;
mqd_t mqd;
// 打开消息队列
mqd = mq_open(MQNAME, O_RDWR);
if (mqd == (mqd_t)-1)
{
perror("mq_open");
exit(-1);
}
// 申请缓冲区内存
buf = (char *)malloc(buf_len);
if (buf == NULL)
{
fprintf(stderr, "malloc\n");
exit(-1);
}
// 发送
strncpy(buf, MSG1, sizeof(MSG1));
if (mq_send(mqd, buf, buf_len, prio1) < 0)
{
perror("mq_send");
exit(-1);
}
strncpy(buf, MSG2, sizeof(MSG2));
if (mq_send(mqd, buf, buf_len, prio2) < 0)
{
perror("mq_send");
exit(-1);
}
free(buf);
mq_close(mqd);
exit(0);
}
接受消息:
/**
* @file 3-mqreceive.c
* 接受消息,阻塞
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string.h>
#include <strings.h>
#define MQNAME "/temp.1234"
int main(int argc, char **argv)
{
char *buf;
long buf_len;
mqd_t mqd;
struct mq_attr attr;
// 打开消息队列
mqd = mq_open(MQNAME, O_RDONLY);
if (mqd == (mqd_t)-1)
{
perror("mq_open");
exit(-1);
}
// 获取消息队列的属性
bzero(&attr, sizeof(attr));
mq_getattr(mqd, &attr);
buf_len = attr.mq_msgsize;
// 给buf申请内存
buf = (char *)malloc(buf_len);
if (buf == NULL)
{
fprintf(stderr, "malloc\n");
mq_close(mqd);
exit(-1);
}
// 接受消息
// prio == 0, 表示接受优先级最高的,可以指定优先级
// 如果指定的优先级不存在,就接受最高优先级
unsigned int prio = 20;
bzero(buf, buf_len);
mq_receive(mqd, buf, buf_len, &prio);
printf("msg:%s\n", buf);
bzero(buf, buf_len);
mq_receive(mqd, buf, buf_len, &prio);
printf("msg:%s\n", buf);
exit(0);
}
使用mq_notify()通知接收消息
/**
* @file 4-1mqnotify.c
* 简单的信号通知
*
* 使用消息队列异步通知
* 1.mq_notify是一次性的,想要多次使用,必须重复注册
* 2.任意时刻任何进程,只能有一个注册接受通知
* 3.空指针代表注销通知
* 4.receive的阻塞比通知优先级高
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <signal.h>
#include <string.h>
#include <strings.h>
#include <mqueue.h>
#include <unistd.h>
#define MQNAME "/temp.1234"
mqd_t mqd;
void *buf;
long buf_len;
struct mq_attr attr;
struct sigevent sigev;
/**
* 信号处理函数
*/
static void sig_usr(int p)
{
// 重新注册
mq_notify(mqd, &sigev);
// 收到信号就去消息队列里取消息
mq_receive(mqd, (char*)buf, buf_len, NULL);
printf("msg:%s\n", buf);
// 清空
bzero(buf, buf_len);
return;
}
int main(int argc, char **argv)
{
// 打开消息队列
mqd = mq_open(MQNAME, O_RDWR);
if (mqd == (mqd_t)-1)
{
perror("mq_open");
exit(-1);
}
// 获取属性
mq_getattr(mqd, &attr);
// 获取最大size
buf_len = attr.mq_msgsize;
// buf申请内存
buf = (void *)malloc(buf_len);
if (buf == NULL)
{
fprintf(stderr, "malloc\n");
exit(-1);
}
// 注册信号
signal(SIGUSR1, sig_usr);
// 设置信号响应
bzero(&sigev, sizeof(sigev));
sigev.sigev_notify = SIGEV_SIGNAL;
sigev.sigev_signo = SIGUSR1;
// 注册通知者
if (mq_notify(mqd, &sigev) < 0)
{
perror("mq_notify");
exit(-1);
}
for ( ;; )
{
sleep(1);
}
exit(0);
}
由于非实时信号会丢失,下列代码使用阻塞信号改进,并改用非阻塞读取:
/**
* @file 4-2mqnotify.c
* 非阻塞的mq_notify的信号通知
* 使用阻塞信号临界区
* 注意信号的很多东西得是gnuc标准
*/
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <errno.h>
#define MQNAME "/temp.1234"
volatile sig_atomic_t mqflag;
static void sig_user1(int p);
int main(int argc, char **argv)
{
mqd_t mqd;
void *buf;
sigset_t zeromask, newmask, oldmask;
struct mq_attr attr;
struct sigevent sigev;
// 非阻塞打开消息队列
mqd = mq_open(MQNAME, O_RDONLY | O_NONBLOCK);
if (mqd == (mqd_t)-1)
{
perror("mq_open");
exit(-1);
}
// 获取消息队列的属性
mq_getattr(mqd, &attr);
// 给存储区申请内存
buf = malloc(attr.mq_msgsize);
// 清空信号屏蔽字
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigemptyset(&oldmask);
// 添加阻塞信号
sigaddset(&newmask, SIGUSR1);
// 注册信号处理函数
signal(SIGUSR1, sig_user1);
// 设置通知信号
sigev.sigev_notify = SIGEV_SIGNAL;
sigev.sigev_signo = SIGUSR1;
// 注册信号通知
mq_notify(mqd, &sigev);
// 循环等待信号
while (1)
{
// 阻塞信号,形成信号临界区代码,这个函数不能在多线程中使用,因为我们应该只改变当前线程的掩码
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
// 这中间得代码不能被信号打断,来的信号全部排队阻塞
// 从阻塞队列里取出一个信号处理
while (mqflag == 0)
{
// 暂停阻塞,暂停线程,直到某个某个信号从处理函数返回
// 相当用从信号临界区,取出一个阻塞的信号,原子地等待它的处理函数执行完毕
// 然后再将临界区封闭,继续阻塞信号
sigsuspend(&zeromask);
}
// 重新设置等待条件
mqflag = 0;
mq_notify(mqd, &sigev);
// 读数据
ssize_t n;
while ((n = mq_receive(mqd, (char*)buf, attr.mq_msgsize, NULL)) >= 0)
{
printf("msg:%s\n", buf);
}
// 判断错误,因为mqd是非阻塞的,所以如果的读的时候没有数据,就以EAGAIN错误返回
// 但是这个错误不是程序错误
if (errno != EAGAIN)
{
perror("mq_receive");
mq_close(mqd);
exit(-1);
}
// 接触阻塞,临界区代码下边界
sigprocmask(SIG_UNBLOCK, &newmask, NULL);
}
exit(0);
}
void sig_user1(int p)
{
mqflag = 1;
return;
}
使用sigwait改进上述代码,提升效率。
/**
* @file 4-3mqnotify.c
* 使用sigwait代替sigsuspend
* 注意,我们一直使用的是signal
* 这个函数是有缺陷的,因为它会在执行一个信号处理函数的时候,被另一个信号打断而跑去执行另一个,
* 虽然可以返回来,但是返回的环境可能会不同。
* 如果不想信号在处理函数中被别的信号打断,应该使用sigaction()函数。
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <signal.h>
#include <errno.h>
#define MQNAME "/temp.1234"
int main(int agrc, char **argv)
{
mqd_t mqd;
void *buf;
struct mq_attr attr;
mqd = mq_open(MQNAME, O_RDONLY | O_NONBLOCK);
if (mqd == (mqd_t)-1)
{
perror("mq_open");
exit(-1);
}
mq_getattr(mqd, &attr);
buf = malloc(attr.mq_msgsize);
if (buf == NULL)
{
fprintf(stderr, "malloc failed.\n");
exit(-1);
}
sigset_t newmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigprocmask(SIG_BLOCK, &newmask, NULL);
struct sigevent sigev;
sigev.sigev_notify = SIGEV_SIGNAL;
sigev.sigev_signo = SIGUSR1;
mq_notify(mqd, &sigev);
while (1)
{
int signo;
// 这个参数是信号集,信号集和信号屏蔽字本质都是sigset_t类型的位图,这里信号集中只有SIGUSR1
// 而做阻塞用的时候,也是只阻塞SIGUSR1
// 这个函数可以用在多线程里,因为它执行的时候会暂时挂起线程,只返回响应的信号,并把信号编号填充signo
sigwait(&newmask, &signo);
if (signo == SIGUSR1)
{
// 读
mq_notify(mqd, &sigev);
int n;
while ((n = mq_receive(mqd, (char *)buf, attr.mq_msgsize, NULL)) >= 0)
{
printf("msg:%s\n", buf);
}
if (errno != EAGAIN)
{
perror("mq_receive");
exit(-1);
}
}
}
}
使用select改进代码:
/**
* @file 4-4mqnotify.c
* 使用select
* 因为mqd_t文件描述符不是普通的文件描述符,不能直接用于select
* 所以我们需要桥接,管道描述符可以用与select,可以使用管道描述符和信号进行桥接
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <mqueue.h>
#include <signal.h>
#include <errno.h>
#define MQNAME "/temp.1234"
int pipefd[2];
static void sig_usr(int p)
{
// 写的目的就是为了触发select的活动
write(pipefd[1], " ", 1);
return;
}
int main(int argc, char **argv)
{
mqd_t mqd;
void *buf;
struct mq_attr attr;
mqd = mq_open(MQNAME, O_RDONLY | O_NONBLOCK);
if (mqd == (mqd_t)-1)
{
perror("mq_open");
exit(-1);
}
mq_getattr(mqd, &attr);
buf = malloc(attr.mq_msgsize);
if (buf == NULL)
{
fprintf(stderr, "malloc failed.\n");
exit(-1);
}
// 新建一个管道
pipe(pipefd);
signal(SIGUSR1, sig_usr);
struct sigevent sigev;
sigev.sigev_notify = SIGEV_SIGNAL;
sigev.sigev_signo = SIGUSR1;
mq_notify(mqd, &sigev);
// 文件描述符集,防止需要select监听的描述符
fd_set fset;
// 先清空
FD_ZERO(&fset);
while (1)
{
// 把管道的读端添加到文件描述符集合
FD_SET(pipefd[0], &fset);
// 最大描述符, 监听读,监听写,监听错误,时间
select(pipefd[0] + 1, &fset, NULL, NULL, NULL);
// 检查响应的是不是pipefd[0]
// 活动的描述符被保留,未活动的会被select设置为0,所以每次循环都要重置fset
// 同样这个fset也会被整个拷贝道内核中
if(FD_ISSET(pipefd[0], &fset))
{
char ch;
read(pipefd[0], &ch, 1); // 把标记字符读出来
mq_notify(mqd, &sigev);
int n;
while ((n = mq_receive(mqd, (char*)buf, attr.mq_msgsize, NULL)) >= 0)
{
printf("msg:%s\n", buf);
}
if (errno != EAGAIN)
{
perror("mq_receive");
exit(-1);
}
}
}
exit(0);
}
正统的mq_notify()用法——调用线程
/**
* @file 4-5mqnotify.c
* 使用mq_notify()的正统用法——调用线程
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/stat.h>
#include <errno.h>
#include <signal.h>
#include <mqueue.h>
#define MQNAME "/temp.1234"
mqd_t mqd;
struct mq_attr attr;
struct sigevent sigev;
static void notify_thread(union sigval sv)
{
int n;
void *buf;
buf = malloc(attr.mq_msgsize);
if (buf == NULL)
{
fprintf(stderr, "malloc failed.\n");
exit(-1);
}
mq_notify(mqd, &sigev);
while ((n = mq_receive(mqd, (char *)buf, attr.mq_msgsize, NULL)) >= 0)
{
printf("msg:%s\n", buf);
}
if (errno != EAGAIN)
{
perror("mq_receive");
exit(-1);
}
free(buf);
pthread_exit(NULL);
}
int main()
{
mqd = mq_open(MQNAME, O_RDONLY | O_NONBLOCK);
if (mqd == (mqd_t)-1)
{
perror("mq_open");
exit(-1);
}
mq_getattr(mqd, &attr);
sigev.sigev_notify = SIGEV_THREAD;
sigev.sigev_value.sival_ptr = NULL;
sigev.sigev_notify_function = notify_thread;
sigev.sigev_notify_attributes = NULL;
mq_notify(mqd, &sigev);
while (1)
{
pause();
}
exit(0);
}