通信之消息队列编程
1:生产者和消费者模式理解
(1) 生产者/消费者模式:需要使用到同步,以及线程,属于多并发行列,产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。 单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。
(2) 解耦:假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。
(3) 支持并发:生产者直接调用消费者的某个方法,还有另一个弊端。由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者就会白白糟蹋大好时光。 使用了生产者/消费者模式之后,生产者和消费者可以是两个独立的并发主体(常见并发类型有进程和线程两种)。生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据。基本上不用依赖消费者的处理速度。其实当初这个模式,主要就是用来处理并发问题的。
(4) 支持忙闲不均:
缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。
(5) 总共理解:生产者生产数据,消费者消费数据,生产者和消费者通过缓存联系起来,是通过消息队列来连接共享队列里面的东西,在队列为空的时候,消费者无法消费signal.notify() //通知生产者,反之对于生产者来是通知消费者来进行消费。
2:POSIX消息队列
(1) POSIX消息队列是独立于XSI消息队列的一套新的消息队列API,让进程可以用消息的方式进行数据交换。
//包含的头文件
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <mqueue.h>
(2)打开或者创建消息队列。
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);
//解释 mq_open这个函数的
(3)发送或者接受消息队列。
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout);
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio, const struct timespec *abs_timeout);
(4)int mq_close(mqd_t mqdes); 关闭一个消息队列。
我们可以使用mq_close来关闭一个消息队列,这里的关闭并非删除了相关文件,关闭之后消息队列在系统中依然存在,我们依然可以继续打开它使用。这跟文件的close和unlink的概念是类似的。
(5)int mq_unlink(const char *name); 使用mq_unlink真正删除一个消息队列。
(6)使用mq_getattr和mq_setattr来查看和设置消息队列。
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr, struct mq_attr *oldattr);
(7) mq_attr结构体是这样的结构:
struct mq_attr {
long mq_flags; /* 只可以通过此参数将消息队列设置为是否非阻塞O_NONBLOCK */
long mq_maxmsg; /* 消息队列的消息数上限 */
long mq_msgsize; /* 消息最大长度 */
long mq_curmsgs; /* 消息队列的当前消息个数 */
};
(8)//挂载消息队列
编译posix mqueue时,要连接运行时库(runtime library),既-lrt选项,
//把消息队列挂载到 /dev/mqueue 下面
gcc -lrt -lpthread recv.c //
编译的时候需要链接一些库,所以我们可以创建Makefile
CFLAGS+=-lrt –lpthread
(9)创建消息队列和接收消息队列
include <fcntl.h>
#include <sys/stat.h> /* For mode constants */
#include <mqueue.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define MQNAME "/mqtest"
int main(int argc, char *argv[])
{
mqd_t mqd;
int ret;
if (argc != 3) {
fprintf(stderr, "Argument error!\n");
exit(1);
}
mqd = mq_open(MQNAME, O_RDWR|O_CREAT, 0600, NULL);
if (mqd == -1) {
perror("mq_open()");
exit(1);
}
ret = mq_send(mqd, argv[1], strlen(argv[1]), atoi(argv[2]));
if (ret == -1) {
perror("mq_send()");
exit(1);
}
exit(0);
}
// gcc -lrt –lpthread 动态链接库来编译
// 存入消息队列
./send zorro 1
./send shrek 2
./send jerry 3
./send zzzzz 1
./send ssssss 2
./send jjjjj 3
cat /dev/mqueue/mqtest 查看消息队列的状态 QSIZE:31 NOTIFY:0 SIGNO:0 NOTIFY_PID:0
(10)接收消息:
#include <fcntl.h>
#include <sys/stat.h> /* For mode constants */
#include <mqueue.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define MQNAME "/mqtest"
int main()
{
mqd_t mqd;
int ret;
int val;
char buf[BUFSIZ];
mqd = mq_open(MQNAME, O_RDWR);
if (mqd == -1) {
perror("mq_open()");
exit(1);
}
ret = mq_receive(mqd, buf, BUFSIZ, &val);
if (ret == -1) {
perror("mq_send()");
exit(1);
}
ret = mq_close(mqd);
if (ret == -1) {
perror("mp_close()");
exit(1);
}
printf("msq: %s, prio: %d\n", buf, val);
exit(0);
}
(10)删除这个消息队列
#include <fcntl.h>
#include <sys/stat.h> /* For mode constants */
#include <mqueue.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define MQNAME "/mqtest"
int main()
{
int ret;
ret = mq_unlink(MQNAME); //删除
if (ret == -1) {
perror("mp_unlink()");
exit(1);
}
exit(0);
}
(11)异步通知机制 使用这个机制,我们就可以让队列在由空变成不空的时候触发一个异步事件,通知调用进程,以便让进程可以在队列为空的时候不用阻塞等待。
int mq_notify(mqd_t mqdes, const struct sigevent *sevp);
#include <pthread.h>
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
static mqd_t mqdes;
void mq_notify_proc(int sig_num)
{
/* mq_notify_proc()是信号处理函数,
当队列从空变成非空时,会给本进程发送信号,
触发本函数执行。 */
struct mq_attr attr;
void *buf;
ssize_t size;
int prio;
struct sigevent sev;
/* 我们约定使用SIGUSR1信号进行处理,
在此判断发来的信号是不是SIGUSR1。 */
if (sig_num != SIGUSR1) {
return;
}
/* 取出当前队列的消息长度上限作为缓存空间大小。 */
if (mq_getattr(mqdes, &attr) < 0) {
perror("mq_getattr()");
exit(1);
}
buf = malloc(attr.mq_msgsize);
if (buf == NULL) {
perror("malloc()");
exit(1);
}
/* 从消息队列中接收消息。 */
size = mq_receive(mqdes, buf, attr.mq_msgsize, &prio);
if (size == -1) {
perror("mq_receive()");
exit(1);
}
/* 打印消息和其优先级。 */
printf("msq: %s, prio: %d\n", buf, prio);
free(buf);
/* 重新注册mq_notify,以便下次可以出触发。 */
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGUSR1;
if (mq_notify(mqdes, &sev) == -1) {
perror("mq_notify()");
exit(1);
}
return;
}
int main(int argc, char *argv[])
{
struct sigevent sev;
if (argc != 2) {
fprintf(stderr, "Argument error!\n");
exit(1);
}
/* 注册信号处理函数。 */
if (signal(SIGUSR1, mq_notify_proc) == SIG_ERR) {
perror("signal()");
exit(1);
}
/* 打开消息队列,注意此队列需要先创建。 */
mqdes = mq_open(argv[1], O_RDONLY);
if (mqdes == -1) {
perror("mq_open()");
exit(1);
}
/* 注册mq_notify。 */
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGUSR1;
if (mq_notify(mqdes, &sev) == -1) {
perror("mq_notify()");
exit(1);
}
/* 主进程每秒打印一行x,等着从消息队列发来异步信号触发收消息。 */
while (1) {
printf("x\n");
sleep(1);
}
}