Linux IPC实践(7) --Posix消息队列

简介: 1. 创建/获取一个消息队列#include /* For O_* constants */#include /* For mode constan...

1. 创建/获取一个消息队列

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>
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);

参数:

   name:  消息队列名字;

   oflag: 与open函数类型, 可以是O_RDONLY, O_WRONLY, O_RDWR, 还可以按位或上O_CREAT, O_EXCL, O_NONBLOCK.

   mode: 如果oflag指定了O_CREAT, 需要指定mode参数;

   attr: 指定消息队列的属性;

返回值:

   成功: 返回消息队列文件描述符;

   失败: 返回-1;


注意-Posix IPC名字限制:

   1. 必须以”/”开头, 并且后面不能还有”/”, 形如:/file-name;

   2. 名字长度不能超过NAME_MAX

   3. 链接时:Link with -lrt.

/** System V 消息队列

通过msgget来创建/打开消息队列

int msgget(key_t key, int msgflg);

**/

 

2. 关闭一个消息队列

int mq_close(mqd_t mqdes);
/** System V 消息队列没有类似的该函数调用**/

3. 删除一个消息队列

int mq_unlink(const char *name);
/** System V 消息队列
通过msgctl函数, 并将cmd指定为IPC_RMID来实现
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
**/
//示例
int main()
{
    mqd_t mqid = mq_open("/abc", O_CREAT|O_RDONLY, 0666, NULL);
    if (mqid == -1)
        err_exit("mq_open error");
    cout << "mq_open success" << endl;
    mq_close(mqid);
    mq_unlink("/abc");
    cout << "unlink success" << endl;
}

4. 获取/设置消息队列属性

int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, struct mq_attr *newattr,
                        struct mq_attr *oldattr);

参数:

   newattr: 需要设置的属性

   oldattr: 原来的属性

//struct mq_attr结构体说明
struct mq_attr
{
    long mq_flags;       /* Flags: 0 or O_NONBLOCK */
    long mq_maxmsg;      /* Max. # of messages on queue: 消息队列能够保存的消息数 */
    long mq_msgsize;     /* Max. message size (bytes): 消息的最大长度 */
    long mq_curmsgs;     /* # of messages currently in queue: 消息队列当前保存的消息数 */
};

/** System V 消息队列

通过msgctl函数, 并将cmd指定为IPC_STAT/IPC_SET来实现

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

**/

/** 示例: 获取消息队列的属性
**/
int main(int argc,char **argv)
{
    mqd_t mqid = mq_open("/test", O_RDONLY|O_CREAT, 0666, NULL);
    if (mqid == -1)
        err_exit("mq_open error");

    struct mq_attr attr;
    if (mq_getattr(mqid, &attr) == -1)
        err_exit("mq_getattr error");
    cout << "Max messages on queue: " << attr.mq_maxmsg << endl;
    cout << "Max message size: " << attr.mq_msgsize << endl;
    cout << "current messages: " << attr.mq_curmsgs << endl;

    mq_close(mqid);
    return 0;
}

5. 发送消息

int mq_send(mqd_t mqdes, const char *msg_ptr,
           size_t msg_len, unsigned msg_prio);

参数:

   msg_ptr: 指向需要发送的消息的指针

   msg_len: 消息长度

   msg_prio: 消息的优先级

/** System V 消息队列

通过msgsnd函数来实现消息发送

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

**/

/** 示例: 向消息队列中发送消息, prio需要从命令行参数中读取 **/
struct Student
{
    char name[36];
    int age;
};
int main(int argc,char **argv)
{
    if (argc != 2)
        err_quit("./send <prio>");

    mqd_t mqid = mq_open("/test", O_WRONLY|O_CREAT, 0666, NULL);
    if (mqid == -1)
        err_exit("mq_open error");

    struct Student stu = {"xiaofang", 23};
    unsigned prio = atoi(argv[1]);
    if (mq_send(mqid, (const char *)&stu, sizeof(stu), prio) == -1)
        err_exit("mq_send error");

    mq_close(mqid);
    return 0;
}

6. 从消息队列中读取消息

ssize_t mq_receive(mqd_t mqdes, char *msg_ptr,
                       size_t msg_len, unsigned *msg_prio);

参数:

  msg_len: 读取的消息的长度, 注意: 此值一定要等于mq_attr::mq_msgsize的值, 该值可以通过mq_getattr获取, 但一般是8192字节     [this must be greater than the mq_msgsize attribute of the queue (see mq_getattr(3)).]

  msg_prio: 保存获取的消息的优先级

返回值:

  成功: 返回读取的消息的字节数

  失败: 返回-1

  注意: 读取的永远是消息队列中优先级最高的最早的消息, 如果消息队列为, 如果不指定为非阻塞模式, 则mq_receive会阻塞;

/** System V 消息队列

通过msgrcv函数来实现消息发送的

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

**/

/** 示例: 从消息队列中获取消息 **/
int main(int argc,char **argv)
{
    mqd_t mqid = mq_open("/test", O_RDONLY);
    if (mqid == -1)
        err_exit("mq_open error");

    struct Student buf;
    int nrcv;
    unsigned prio;
    struct mq_attr attr;
    if (mq_getattr(mqid, &attr) == -1)
        err_exit("mq_getattr error");

    if ((nrcv = mq_receive(mqid, (char *)&buf, attr.mq_msgsize, &prio)) == -1)
        err_exit("mq_receive error");

    cout << "receive " << nrcv << " bytes, priority: " << prio << ", name: "
         << buf.name << ", age: " << buf.age << endl;

    mq_close(mqid);
    return 0;
}

7. 建立/删除消息到达通知事件

int mq_notify(mqd_t mqdes, const struct sigevent *sevp);

参数sevp:

   NULL: 表示撤销已注册通知;

   非空: 表示当消息到达且消息队列当前为空, 那么将得到通知;

通知方式:

   1. 产生一个信号, 需要自己绑定

   2. 创建一个线程, 执行指定的函数

注意: 这种注册的方式只是在消息队列从空到非空时才产生消息通知事件, 而且这种注册方式是一次性的!

//sigevent结构体
struct sigevent
{
    int          sigev_notify; /* Notification method */
    int          sigev_signo;  /* Notification signal */
    union sigval sigev_value;  /* Data passed with notification */
    void       (*sigev_notify_function) (union sigval);  /* Function used for thread notification (SIGEV_THREAD) */
    void        *sigev_notify_attributes; /* Attributes for notification thread (SIGEV_THREAD) */
    pid_t        sigev_notify_thread_id; /* ID of thread to signal (SIGEV_THREAD_ID) */
};
union sigval            /* Data passed with notification */
{
    int     sival_int;         /* Integer value */
    void   *sival_ptr;         /* Pointer value */
};

sigev_notify代表通知的方式: 一般常用两种取值:SIGEV_SIGNAL, 以信号方式通知; SIGEV_THREAD, 以线程方式通知

如果以信号方式通知: 则需要设定一下两个参数:

   sigev_signo: 信号的代码

   sigev_value: 信号的附加数据(实时信号)

如果以线程方式通知: 则需要设定以下两个参数:

   sigev_notify_function

   sigev_notify_attributes

/** Posix IPC所特有的功能, System V没有 **/

/**示例: 将下面程序多运行几遍, 尤其是当消息队列”从空->非空”, 多次”从空->非空”, 当消息队列不空时运行该程序时, 观察该程序的状态;
**/
mqd_t mqid;
long size;
void sigHandlerForUSR1(int signo)
{
    //将数据的读取转移到对信号SIGUSR1的响应函数中来
    struct Student buf;
    int nrcv;
    unsigned prio;
    if ((nrcv = mq_receive(mqid, (char *)&buf, size, &prio)) == -1)
        err_exit("mq_receive error");

    cout << "receive " << nrcv << " bytes, priority: " << prio << ", name: "
         << buf.name << ", age: " << buf.age << endl;
}

int main(int argc,char **argv)
{
    // 安装信号响应函数
    if (signal(SIGUSR1, sigHandlerForUSR1) == SIG_ERR)
        err_exit("signal error");

    mqid = mq_open("/test", O_RDONLY);
    if (mqid == -1)
        err_exit("mq_open error");

    // 获取消息的最大长度
    struct mq_attr attr;
    if (mq_getattr(mqid, &attr) == -1)
        err_exit("mq_getattr error");
    size = attr.mq_msgsize;

    // 注册消息到达通知事件
    struct sigevent event;
    event.sigev_notify = SIGEV_SIGNAL;  //指定以信号方式通知
    event.sigev_signo = SIGUSR1;        //指定以SIGUSR1通知
    if (mq_notify(mqid, &event) == -1)
        err_exit("mq_notify error");

    //死循环, 等待信号到来
    while (true)
        pause();

    mq_close(mqid);
    return 0;
}
/** 示例:多次注册notify, 这样就能过多次接收消息, 但是还是不能从队列非空的时候进行接收, 将程序改造如下:
**/
mqd_t mqid;
long size;
struct sigevent event;
void sigHandlerForUSR1(int signo)
{
    // 注意: 是在消息被读走之前进行注册,
    // 不然该程序就感应不到消息队列"从空->非空"的一个过程变化了
    if (mq_notify(mqid, &event) == -1)
        err_exit("mq_notify error");

    //将数据的读取转移到对信号SIGUSR1的响应函数中来
    struct Student buf;
    int nrcv;
    unsigned prio;
    if ((nrcv = mq_receive(mqid, (char *)&buf, size, &prio)) == -1)
        err_exit("mq_receive error");

    cout << "receive " << nrcv << " bytes, priority: " << prio << ", name: "
         << buf.name << ", age: " << buf.age << endl;
}

int main(int argc,char **argv)
{
    // 安装信号响应函数
    if (signal(SIGUSR1, sigHandlerForUSR1) == SIG_ERR)
        err_exit("signal error");

    mqid = mq_open("/test", O_RDONLY);
    if (mqid == -1)
        err_exit("mq_open error");

    // 获取消息的最大长度
    struct mq_attr attr;
    if (mq_getattr(mqid, &attr) == -1)
        err_exit("mq_getattr error");
    size = attr.mq_msgsize;

    // 注册消息到达通知事件
    event.sigev_notify = SIGEV_SIGNAL;  //指定以信号方式通知
    event.sigev_signo = SIGUSR1;        //指定以SIGUSR1通知
    if (mq_notify(mqid, &event) == -1)
        err_exit("mq_notify error");

    //死循环, 等待信号到来
    while (true)
        pause();

    mq_close(mqid);
    return 0;
}

mq_notify 注意点总结:

   1. 任何时刻只能有一个进程可以被注册为接收某个给定队列的通知;

   2. 当有一个消息到达某个先前为空的队列, 而且已有一个进程被注册为接收该队列的通知时, 只有没有任何线程阻塞在该队列的mq_receive调用的前提下, 通知才会发出;

   3. 当通知被发送给它的注册进程时, 该进程的注册被撤销. 进程必须再次调用mq_notify以重新注册(如果需要的话),但是要注意: 重新注册要放在从消息队列读出消息之前而不是之后(如同示例程序);

 

附-查看已经成功创建的Posix消息队列

#其存在与一个虚拟文件系统中, 需要将其挂载到系统中才能查看

  Mounting the message queue filesystem On Linux, message queues are created in a virtual filesystem.  

(Other implementations may also  provide such a feature, but the details are likely to differ.)  This 

file system can be mounted (by the superuser, 注意是使用root用户才能成功) using the following commands:

mkdir /dev/mqueue

mount -t mqueue none /dev/mqueue

还可以使用cat查看该消息队列的状态, rm删除:

cat /dev/mqueue/abc 

rm abc

还可umount该文件系统

umount /dev/mqueue

 

附-Makefile

.PHONY: clean all
CC = g++
CPPFLAGS = -Wall -g
BIN = main
SOURCES = $(BIN.=.cpp)
all: $(BIN)

%.o: %.c
	$(CC) $(CPPFLAGS) -c $^ -o $@
main: main.o
	$(CC) $(CPPFLAGS) $^ -lrt -o $@

clean:
	-rm -rf $(BIN) *.o bin/ obj/ core

相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
目录
相关文章
|
1天前
|
消息中间件 Java 开发工具
【实践】快速学会使用云消息队列RabbitMQ版
本次分享的主题是快速学会使用云消息队列RabbitMQ版的实践。内容包括:如何创建和配置RabbitMQ实例,如Vhost、Exchange、Queue等;如何通过阿里云控制台管理静态用户名密码和AccessKey;以及如何使用RabbitMQ开源客户端进行消息生产和消费测试。最后介绍了实验资源的回收步骤,确保资源合理利用。通过详细的操作指南,帮助用户快速上手并掌握RabbitMQ的使用方法。
30 10
|
16天前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####
|
16天前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
1月前
|
关系型数据库 MySQL Linux
Linux环境下MySQL数据库自动定时备份实践
数据库备份是确保数据安全的重要措施。在Linux环境下,实现MySQL数据库的自动定时备份可以通过多种方式完成。本文将介绍如何使用`cron`定时任务和`mysqldump`工具来实现MySQL数据库的每日自动备份。
94 3
|
2月前
|
消息中间件 安全 Java
云消息队列RabbitMQ实践解决方案评测
一文带你详细了解云消息队列RabbitMQ实践的解决方案优与劣
90 9
|
1月前
|
消息中间件 存储 Linux
|
2月前
|
消息中间件
解决方案 | 云消息队列RabbitMQ实践获奖名单公布!
云消息队列RabbitMQ实践获奖名单公布!
|
2月前
|
监控 Linux 云计算
Linux操作系统在云计算环境中的实践与优化###
【10月更文挑战第16天】 本文探讨了Linux操作系统在云计算环境中的应用实践,重点分析了其在稳定性、安全性和高效性方面的优势。通过具体案例,阐述了Linux如何支持虚拟化技术、实现资源高效分配以及与其他开源技术的无缝集成。文章还提供了针对Linux系统在云计算中的优化建议,包括内核参数调整、文件系统选择和性能监控工具的应用,旨在帮助读者更好地理解和应用Linux于云计算场景。 ###
54 3
|
2月前
|
消息中间件 存储 弹性计算
云消息队列RabbitMQ实践
云消息队列RabbitMQ实践
|
2月前
|
消息中间件 存储 弹性计算
云消息队列 RabbitMQ 版实践解决方案评测
随着企业业务的增长,对消息队列的需求日益提升。阿里云的云消息队列 RabbitMQ 版通过架构优化,解决了消息积压、内存泄漏等问题,并支持弹性伸缩和按量计费,大幅降低资源和运维成本。本文从使用者角度详细评测这一解决方案,涵盖实践原理、部署体验、实际优势及应用场景。
下一篇
DataWorks