Posix消息队列使用总结

Posix在线文档:

The Single UNIX Specification, Version 2 (opengroup.org)

Linux系统中提供了两种不同接口的消息队列:

  1. POSIX消息队列。POSIX为可移植的操作系统接口。
  2. System V消息队列。System V 是 AT&T 的第一个商业UNIX版本(UNIX System III)的加强。

其中,POSIX消息队列可移植性较强,使用较广。

Linux系统中提供的消息队列一般应用于进行间通信,但也可以用于线程间通信。

本文介绍POSIX消息队列应用于线程间通信。

相关API

Linux内核提供了一系列函数来使用消息队列:

/**
 * @brief 创建消息队列实例
 *
 * Detailed function description
 *
 * @param[in] name: 消息队列名称
 * @param[in] oflag:根据传入标识来创建或者打开一个已创建的消息队列
                    - O_CREAT: 创建一个消息队列
                    - O_EXCL: 检查消息队列是否存在,一般与O_CREAT一起使用
                    - O_CREAT|O_EXCL: 消息队列不存在则创建,已存在返回NULL
                    - O_NONBLOCK: 非阻塞模式打开,消息队列不存在返回NULL
                    - O_RDONLY: 只读模式打开
                    - O_WRONLY: 只写模式打开
                    - O_RDWR: 读写模式打开
 * @param[in] mode:访问权限
 * @param[in] attr:消息队列属性地址
 *
 * @return 成功返回消息队列描述符,失败返回-1,错误码存于error中
 */
mqd_t mq_open(const char *name, int oflag,  mode_t mode, struct mq_attr *attr);

/**
 * @brief 无限阻塞方式接收消息
 *
 * Detailed function description
 *
 * @param[in] mqdes: 消息队列描述符
 * @param[in] msg_ptr:消息体缓冲区地址
 * @param[in] msg_len:消息体长度,长度必须大于等于消息属性设定的最大值
 * @param[in] msg_prio:消息优先级
 *
 * @return 成功返回消息长度,失败返回-1,错误码存于error中
 */
mqd_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio);

/**
 * @brief 指定超时时间阻塞方式接收消息
 *
 * Detailed function description
 *
 * @param[in] mqdes: 消息队列描述符
 * @param[in] msg_ptr:消息体缓冲区地址
 * @param[in] msg_len:消息体长度,长度必须大于等于消息属性设定的最大值
 * @param[in] msg_prio:消息优先级
 * @param[in] abs_timeout:超时时间
 *
 * @return 成功返回消息长度,失败返回-1,错误码存于error中
 */
mqd_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio, const struct timespec *abs_timeout);

/**
 * @brief 无限阻塞方式发送消息
 *
 * Detailed function description
 *
 * @param[in] mqdes: 消息队列描述符
 * @param[in] msg_ptr:待发送消息体缓冲区地址
 * @param[in] msg_len:消息体长度
 * @param[in] msg_prio:消息优先级
 *
 * @return 成功返回0,失败返回-1
 */
mqd_t mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio);

/**
 * @brief 指定超时时间阻塞方式发送消息
 *
 * Detailed function description
 *
 * @param[in] mqdes: 消息队列描述符
 * @param[in] msg_ptr:待发送消息体缓冲区地址
 * @param[in] msg_len:消息体长度
 * @param[in] msg_prio:消息优先级
 * @param[in] abs_timeout:超时时间
 *
 * @return 成功返回0,失败返回-1
 */
mqd_t mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio, const struct timespec *abs_timeout);

/**
 * @brief 关闭消息队列
 *
 * Detailed function description
 *
 * @param[in] mqdes: 消息队列描述符
 *
 * @return 成功返回0,失败返回-1
 */
mqd_t mq_close(mqd_t mqdes);

/**
 * @brief 分离消息队列
 *
 * Detailed function description
 *
 * @param[in] name: 消息队列名称
 *
 * @return 成功返回0,失败返回-1
 */
mqd_t mq_unlink(const char *name);

消息队列的名称类似于设备文件,mq_open通过名称打开对应的消息队列,然后返回一个消息队列描述符,之后的操作都是引用返回的描述符,类似于打开文件时返回的文件描述符。

其实和文件的打开、读写、关闭都非常类似。

​POSIX消息队列允许进程以消息的形式交换数据。 消息队列是使用mq_open创建和打开的;此函数返回消息队列描述符(mqd_t),该描述符用于在以后的调用中引用打开的消息队列。每个消息队列由/somename形式的名称标识,该名称由一个初始斜杠组成,后跟一个或多个字符(都不是斜杠)。通过将相同的名称传递给mq_open,两个进程可以在同一队列上进行操作。 使用mq_send和mq_receive在队列之间来回传送消息。当进程使用完队列后,可以使用mq_close关闭该队列,而当不再需要该队列时,可以使用mq_unlink将其删除。可以使用mq_getattr和mq_setattr检索和修改队列属性(在某些情况下)。进程可以使用mq_notify请求异步通知消息到达先前为空的队列。 ​

消息队列可以认为是一个链表。进程(线程)可以往里写消息,也可以从里面取出消息。一个进程可以往某个消息队列里写消息,然后终止,另一个进程随时可以从消息队列里取走这些消息。这里也说明了,消息队列具有随内核的持续性,也就是系统不重启,消息队列永久存在。

相关头文件如下:

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>

更详细使用参考:POSIX消息队列详解与示例_posix 消息队列-CSDN博客

mq_open()

#include<fcntl.h>
#include<sys/stat.h>
#include<mqueue.h>

mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);
// 返回消息队列描述符,失败返回-1

在fork()中子进程会接收其父进程的消息队列描述符的副本,并且这些描述符会引用同样 的打开着的消息队列描述符。子进程不会继承其父进程的任何消息通知注册;
当一个进程执行了一个exec()或终止时,所有其打开的消息队列描述符会被关闭。
name:对象名,最大长度为NAME_MAX(255),一般为“/”开头,后面跟不含"/"的名字,如/ipc_name

oflag:位掩码,它控制着mq_open()操作的各个方面:

标记描述
O_CREAT队列不存在时创建队列
O_EXCL与O_CREAT一起使用,若消息队列已存在,则错误返回
O_RDONLY只读打开
O_WRONLY只写打开
O_RDWR读写打开
O_NONBLOCK以非阻塞模式打开

mode:指定了文件的访问权限,如果flags未指定O_CREAT标识,则可省略mode参数

​attr:如果flags未指定O_CREAT标识,则可省略attr参数。为NULL时使用/proc/sys/fs/mqueue/下的默认参数,最大消息数等限制也在这个文件夹下

struct mq_attr
{
    long mq_flags;		//阻塞标志,0或O_NONBLOCK,只能通过mq_setattr()设置
    long mq_maxmsg;		//最大消息数,只能通过mq_open()设置
    long mq_msgsize;	//每个消息最大大小,只能通过mq_open()设置
    long mq_curmsgs;	//当前消息数
};

不同进程的mq_flags是独立的

mq_close()

#include<mqueue.h>

int mq_close(mqd_t mqdes);
// 成功返回0,失败返回-1

与文件上的close()一样,关闭一个消息队列并不会删除该队列。要删除队列则需要使用mq_unlinl(),它是unlink()在消息队列上的版本。

mq_unlink()

#include<mqueue.h>

int mq_unlink(const char *name);
// 成功返回0,失败返回-1

mq_unlink()函数删除通过name标识的消息队列,并将队列标记为在所有进程使用完该队列之后销毁该队列。

mq_getattr()

#include<mqueue.h>

int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
// 成功返回0,失败返回-1

struct mq_attr
{
    long mq_flags;		//阻塞标志,0或O_NONBLOCK,只能通过mq_setattr()设置
    long mq_maxmsg;		//最大消息数,只能通过mq_open()设置
    long mq_msgsize;	//每个消息最大大小,只能通过mq_open()设置
    long mq_curmsgs;	//当前消息数
};

mq_curmsgs:当前位于队列中的消息数。这个信息在mq_getattr()返回时可能已经发生了改变,前提是存在其他进程从队列中读取消息或写入消息

mq_setattr()

#include<mqueue.h>

int mq_setattr(mq_t mqdes, const struct mq_attr *newattr, struct mq_attr *oldattr);
// 成功返回0,失败返回-1

mq_setattr()函数执行下列任务:

它使用newattr指向的mq_attr结构中的mq_flags字段来修改与描述符mqdes相关联的消息队列描述的标记
如果oldattr不为NULL,那么就返回一个包含之前的消息队列描述标记和消息队列特性的mq_attr结构(即于mq_getattr()效果相同)
SUSv3规定使用mq_setattr()能够修改的唯一特性是O_NONBLOCK标记的状态

mq_send()

#include<mqueue.h>

int mq_send(mqd_t mqdes, const char *msg_ptr,size_t msg_len, unsigned int msg_prio);
// 成功返回0,失败返回-1

msg_len参数指定了msg_ptr指向的消息的长度,其值必须小于或等于队列的mq_msgsize特性,否则mq_send()就会返回EMSGSIZE错误。长度为零的消息是允许的。

每条消息都拥有一个用非负整数表示的优先级,它通过msg_prio参数指定。消息在队列中是按照优先级倒序排列的(即0表示优先级最低)。当一条消息被添加到队列中时,它会被放置在队列中具有相同优先级的所有消息之后。如果一个应用程序无需使用消息优先级,那么只需要将msg_prio指定为0即可。

如果消息队列满了,阻塞模式下将一直阻塞,非阻塞模式下直接返回错误。

mq_receive()

#include<mqueue.h>

ssize_t mq_receive(mqd_t mqdes, char *mdg_ptr,size_t msg_len, unsigned int *msg_prio);
// 返回接收长度,失败返回-1

mq_receive()函数从mqdes引用的消息队列中删除一条优先级最高、存在时间最长的消息并将删除的消息放置在msg_ptr指向的缓冲区。

使用msg_len参数来指定msg_ptr指向的缓冲区中的可用字节数。

不管消息的实际大小是什么,msg_len必须要大于或等于队列的mq_msgsize特性,否则mq_receive()就会失败并返回EMSGSIZE错误。如果不清楚一个队列的mq_msgsize特性的值,那么可以使用mq_getattr()来获取这个值。

如果msg_prio不为NULL,那么接收到的消息的优先级会被复制到msg_prio指向的位置处。

mq_notify()

#include<mqueue.h>

int mq_notify(mqd_t mqdes, const struct sigevent *notification);
// 成功返回0,失败返回-1

struct sigevent
{
    int sigev_notify;
    int sigev_signo;
    union sigval sigev_value;
    void (*sigev_notify_function)(union sigval);
    pthread_attr_t *sigev_notify_attributes;
};

sigev_notify:

POSIX消息队列能够接收之前为空的队列上有可用消息的异步通知(即队列从空变成了非空)。

这个特性意味着已经无需执行一个阻塞的调用或将消息队列描述符标记为非阻塞并在队列上定期执行mq_receive()调用。

进程可以选择通过信号的形式或者通过一个单独的线程中调用一个函数的形式来接收通知。

注意事项

参考:

POSIX 消息队列函数(mq_open、mq_getattr、mq_send、mq_receive)示例_mqreceivetest-CSDN博客

关于消息队列mq_open的属性参数

指向attr的struct mq_attr字段指定队列允许的最大消息数和最大消息大小。此结构定义如下:

struct mq_attr {

    long mq_flags;       /* Flags (ignored for mq_open()) */

    long mq_maxmsg;      /* Max. # of messages on queue */

    long mq_msgsize;     /* Max. message size (bytes) */

    long mq_curmsgs;     /* # of messages currently in queue(ignored for mq_open()) */

};

mq_maxmsg指的是容纳的消息数也就是消息队列的缓冲大小,msgsize指的是每个消息的大小;mq_maxmsg貌似并没有那么重要,因为消息队列是以流的形式来发送和接收的。两个参数的乘积,就是消息队列的总容量。

调用mq_open()时仅使用mq_maxmsg和mq_msgsize字段;其余字段中的值将被忽略。

如果attr为NULL,则使用默认属性创建队列。

关于消息队列名称

资料中说消息队列名称要写斜杠/开头,但是我看有的程序里并没有,而是一个单纯的字符串,为啥?

经过测试,在Linux 2.6.18中,所创建的POSIX消息队列不会在文件系统中创建真正的路径名。且POSIX的名字只能以一个’/’开头,名字中不能包含其他的’/’。

比如,不符合的情况:


报错

对应的错误如下:

另外需要注意以下几点:

1、

mq_receive() 的第三个参数表示读取消息的长度,不能小于能写入队列中消息的最大大小,即一定要大于等于该队列的 mq_attr 结构中 mq_msgsize 的大小。也就是最少要读一个消息。

2、

消息的优先级:它是一个小于 MQ_PRIO_MAX 的数,数值越大,优先级越高。 POSIX 消息队列在调用 mq_receive 时总是返回队列中最高优先级的最早消息。如果消息不需要设定优先级,那么可以在 mq_send 是置 msg_prio 为 0, mq_receive 的 msg_prio 置为 NULL。因为mq_send的优先级是以变量的形式传递的,而mq_receive是以指针的形式传递的。

3、

默认情况下mq_send和mq_receive是阻塞进行调用,可以通过mq_setattr来设置为O_NONBLOCK,如:

struct mq_attr new_attr;

mq_getattr(mqID, &new_attr);//获取当前属性

new_attr.mq_flags = O_NONBLOCK;//设置为非阻塞

mq_setattr(mqID, &new_attr, NULL)//设置属性

4、

mq_getattr用于获取当前消息队列的属性,mq_setattr用于设置当前消息队列的属性。其中mq_setattr中的oldattr用于保存修改前的消息队列的属性,可以为空。

mq_setattr可以设置的属性只有mq_flags,用来设置或清除消息队列的非阻塞标志。newattr结构的其他属性被忽略。mq_maxmsg和mq_msgsize属性只能在创建消息队列时通过mq_open来设置。mq_open只会设置该两个属性,忽略另外两个属性。mq_curmsgs属性只能被获取而不能被设置。

5、

mq_send和mq_recevie的第二个参数,也就是数据指针,是char *类型的,需要时做个强转。

6、

使用POSIX消息队列API的程序必须使用cc -lrt进行编译,以链接到实时库librt。

gcc pthread_5.c -o obj -lpthread -lrt

Linux编译命令-pthread & -lpthread - 邗影 - 博客园 (cnblogs.com)

linux的各种自带库-lz -lrt -lm -lc都是什么库_lm是什么库-CSDN博客

7、

消息队列无需init初始化,只用在第一次mq_open打开时创建即可。

8、

Posix消息队列就是先进先出,但是可以通过优先级来让紧急数据插队。

使用示例

常规示例代码如下: 

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>
#include <errno.h>

#define MQ_NAME "/thisIsOneMqName"

static void *thread_fun1(void *param)
{
    mqd_t mqfd;
    struct mq_attr attr;
    int count;

    attr.mq_maxmsg = 1;
    attr.mq_msgsize = sizeof(int);

    mqfd = mq_open(MQ_NAME, O_WRONLY | O_CREAT, 0666, &attr);
    if(mqfd < 0)
    {
        printf("mq_open errno is %d\n", errno);
        exit(-1);
    }

    while(1)
    {
        count += 10;
        mq_send(mqfd, (char *)&count, sizeof(int), 0);
    }
    
    return NULL;
}

static void *thread_fun2(void *param)
{
    mqd_t mqfd;
    int count;

    mqfd = mq_open(MQ_NAME, O_RDONLY);
    if(mqfd < 0)
    {
        printf("mq_open errno is %d\n", errno);
        exit(-1);
    }

    while(1)
    {
        mq_receive(mqfd, (char *)&count, sizeof(int), NULL);
        printf("count is %d\n", count);
        if(count == 100)
        {
            exit(0);
        }
    }
    
    return NULL;
}

int main(int argc, char *argv[])
{
    pthread_t tid1, tid2;
    int ret;

    ret = pthread_create(&tid1, NULL, thread_fun1, NULL);
    if (ret < 0)
    {
         printf("ERROR; return code from pthread_create() is %d\n", ret);
         exit(-1);
    }

    ret = pthread_create(&tid2, NULL, thread_fun2, NULL);
    if (ret < 0)
    {
         printf("ERROR; return code from pthread_create() is %d\n", ret);
         exit(-1);
    }

    pthread_exit(NULL);
}

更多待补充

注意区分ulink函数和mq_ulink函数。

文件删除与数据安全性:深入了解 unlink 函数_unlink系统调用-CSDN博客

从 unlink/rm 底层实现来看Linux文件系统管理_linux系统文件被unlink删除之后还能正常写入吗-CSDN博客

linux - shell 中 rm 和 unlink 的区别是什么?什么情况下只能用 unlink 而不能用 rm ? - SegmentFault 思否

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值