linux进程间通信--消息队列(POSIX 版本)

linux进程间通信–消息队列(POSIX 版本)

参考文件:man mq_overview

由于System V消息存在一些缺点。因此POSIX标准又重新定义了一套消息队列接口。下面将详细介绍POSIX接口下的消息队列。

消息队列有如下的特点:

  1. 适用于任何进程将数据交互。
  2. 以消息为单位进行数据交互。
  3. 可以使用异步的信号通知。
  4. POSIX消息队列的每个消息都可以设置优先级。范围为0 ~ MQ_PRIO_MAX-1。值越大优先级越高。

消息队列与系统相关的一些杂项说明:

  1. 系统会限制所有的消息队列最多可消耗的系统空间。由RLIMIT_MSGQUEUE宏决定。可通过getrlimit()函数获取。
  2. 有关消息队列的相关参数可以查看/proc/sys/fs/mqueue/目录下的文件。如cat msg_max.
    1. msg_default:默认的消息数。当mq_open()时attr参数等于NULL,则mq_attr.mq_maxmsg属性使用此值。默认为10.
    2. msg_max:消息队列最大数量的上限值。即mq_attr.mq_maxmsg的最大值
    3. msgsize_default:消息队列中每个消息的默认消息大小。当mq_open()时attr参数等于NULL,则mq_attr.mq_msgsize属性使用此值。默认为8192.
    4. msgsize_max:消息队列中每个消息的最大值。mq_attr.mq_msgsize的最大值。
    5. queues_max:系统可创建的消息队列上限值。默认256.
  3. 消息队列默认创建的路径为/dev/mqueue.如果没有,可以使用下面的命令挂载(需要root权限):
        mkdir /dev/mqueue
        mount -t mqueue none /dev/mqueue
    

消息队列属性结构体介绍:在mq_open(),mq_getattr(),mq_setattr()等函数都会用到。

//消息队列属性结构体,
struct mq_attr {
    long mq_flags;       //0 or O_NONBLOCK
    long mq_maxmsg;      //最大的消息数
    long mq_msgsize;     //每个消息最大的字节数
    long mq_curmsgs;     //队列中的当前消息数
};

消息队列相关函数介绍:

  1. mqd_t mq_open(const char *mqName, int oflags, ...)

    1. 创建或打开一个消息队列。
    2. mqName:消息队列名称.格式如/xxx。由/开始,并且只能有一个/。如/mq_test.
    3. oflags:表示打开的方式.可以是下面的各种组合
      1. O_RDONLY.只读
      2. O_WRONLY.只写
      3. O_RDWR. 读写
      4. O_CREAT. 如果不存在则创建
      5. O_EXCL. 配合O_CREAT使用,只有不存在时才创建。存在的返回失败
      6. O_NONBLOCK. 不阻塞
    4. 可选参数:当oflags & O_CREAT == true时使用
      1. mode:权限。如0666
      2. attr:指向用于初始化的mq_attr的指针。用于设置mq_maxmsg属性和mq_msgsize属性。其他属性忽略。
    5. 注意:在创建消息队列时,mq_maxmsg和mq_msgsize属性会被确定,后期无法通过mq_setattr()设置。
    6. return:消息队列描述符或-1(错误)
  2. int mq_close(mqd_t mqdes)

    1. 关闭一个消息队列.和文件的close类型一样,关闭后,消息队列并不从系统中删除。一个进程结束,会自动调用关闭打开着的消息队列。
    2. mqdes:队列描述符
    3. return:if true:0,if false:-1.
  3. int mq_unlink(const char *mqName)

    1. 删除一个消息队列。
    2. return:if true:0,if false:-1.
  4. int mq_send(mqd_t mqdes, const void *msg, size_t msglen, int prio)

    1. 发送一个消息msg到消息队列mqdes。如果消息队列已满,并且消息队列没有设置O_NONBLOCK,则mq_send()将阻塞,直到消息队列中有了可用的空间。如果消息队列已满且设置了O_NONBLOCK,则不会对消息进行排队并返回ERROR。
    2. msg:消息内容
    3. msglen:消息内容大小(byte)。最大为mq_getattr()->mq_msgsize。
    4. prio:优先级.范围为0 ~ MQ_PRIO_MAX-1。值越大优先级越高。
    5. return:if true:0,if false:-1.errno如下:
      1. EAGAIN:队列非阻塞,并且已满
      2. EMSGSIZE:消息长度大于最大长度。
      3. EINTR:由信号中断返回。
      4. EPERM:没有权限
  5. int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len,unsigned msg_prio, const struct timespec *abstime);

    1. 功能类似mq_send().只是在没有设置O_NONBLOCK,当队列满后超过等待时间则返回。
    2. return:if true:0,if false:-1.errno除了mq_send()提到的,还有如下:
      1. ETIMEDOUT:超时返回
  6. ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio)

    1. 从消息队列mqdes接收消息。如果消息队列为空,并且消息队列没有设置O_NONBLOCK,则mq_receive()将阻塞,直到消息队列中有了可用的空间。如果消息队列为空且设置了O_NONBLOCK,则直接返回ERROR。
    2. msg_ptr:要接收消息的buff.msg_len:为buff的长度。如果这个长度小于消息本身的长度,则返回错误。
    3. msg_prio:接收消息本身的优先级。
    4. return:if true:接收到的消息大小,if false:-1.errno类似mq_send()。
  7. ssize_t mq_timedreceive(mqd_t mqdes, char *restrict msg_ptr,size_t msg_len, unsigned *restrict msg_prio,const struct timespec *restrict abstime)

    1. 功能类似mq_receive().只是在没有设置O_NONBLOCK,当队列空后超过等待时间则返回。
    2. abstime:绝对时间
    3. return:类似mq_receive()。
  8. int mq_getattr(mqd_t mqdes, struct mq_attr *mqstat)

    1. 获取息队列关联的状态信息和属性。
    2. mqstat:获取信息的buff.
    3. return:if true:0,if false:-1.
  9. int mq_setattr(mqd_t mqdes, const struct mq_attr *restrict mqstat,struct mq_attr *restrict omqstat)

    1. 设置消息队列的属性。只能更改mq_flags的O_NONBLOCK位。其他的属性会被忽略
    2. mqdes:消息队列描述符
    3. mqstat:要设置的值buff.
    4. omqstat:如果不为NULL,则将先前的存在此buff中。
    5. return:if true:0,if false:-1.

消息队列的异步通知

当一个消息队列从空变成非空时,调用进程可以接收一个通知信号。从而达到异步触发的效果。mq_notify()函数用于注册通知到消息队列。需要注意:

  1. 一个消息队列只能被一个进程注册通知. 如果之前调用进程或者其他进程已经注册过了,那么随后调用本函数注册通知会返回失败.如果mq_notify()函数的参数notification是NULL并且这个进程之前注册过了通知函数, 那么这个注册会被删除,其他进程就可以在这个消息队列上注册通知.
  2. 当一个消息队列从空变成非空,并且这个消息队列注册了通知,并且一些线程被函数mq_receive()或者mq_timedreceive() 阻塞了,此时到达的消息会发送给mq_receive()或者mq_timedreceive(),但不会发送通知(结果就像消息队列仍然是空的).

消息通知使用到的函数如下:

  1. int mq_notify(mqd_t mqdes, const struct sigevent *notification)
    1. 一个消息队列从空变成非空时,通知进程可以接收一条消息.如果参数notification不为空, 这个函数会给调用进程注册一个异步通知函数,通知注册进程空的消息队列中有消息到达了.当消息队列从空到非空状态变化时,通知就会发送到注册的进程.
    2. notification: 用来注册通知,当该参数被设置为NULL时,会取消调用进程之前注册的通知。结构体介绍见下面:
    3. return:if true:0,if false:-1.errno如下:
      1. EBADF参数mqdes不是有效的消息队列描述符.
      2. EBUSY:一个进程已经在这个消息队列上注册了通知函数.
      3. EINVAL:参数notification 是NULL 并且 当前进程没有注册通知函数.

消息队列的异步通知结构体如下:

struct sigevent {
    int           sigev_notify;           //通知类型
    int           sigev_signo;            //Signal number. 
    union sigval  sigev_value;             //Signal value. 
    void         (*sigev_notify_function)(union sigval); //Notification function. 
    pthread_attr_t *sigev_notify_attributes;  //Notification attributes. 
};
union sigval{
    int sival_int;
    void *sival_ptr;
};

结构体的各个字段介绍如下:

  1. sigev_notify:通知类型,有如下几种类型
    1. SIGEV_NONE:事件发生时什么也不做。
    2. SIGEV_SIGNAL:事件发生时,将sigev_signo 指定的信号(A queued signal)发送给指定的进程.
    3. SIGEV_THREAD:事件发生时,内核会(在此进程内)以sigev_notification_attributes为线程属性创建一个线程,并且让它执行sigev_notify_function,传入sigev_value作为为一个参数.
  2. sigev_signo:在sigev_notify = SIGEV_SIGNAL 时使用,指定信号的种别(number).
  3. sigev_value:在sigev_notify = SIGEV_THREAD 时使用,作为sigev_notify_function的参数.结构如下
  4. (*sigev_notify_function)(union sigval):函数指针(指向通知执行函数),在sigev_notify = SIGEV_THREAD 时使用, 其他情况下置为NULL.
  5. sigev_notify_attributes:指向线程属性的指针,在sigev_notify = SIGEV_THREAD 时使用,指定创建线程的属性, 其他情况下置为NULL.

消息队列的使用例程如下。编译时需要用到-lrt库。运行时需要root权限。

#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <error.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <mqueue.h>
#include <string.h>

//共享数据结构体
struct shm_data{
    int count;
    char buff[32];
};

//消息队列写进程
void process_write(void)
{
    struct shm_data data_obj;
    struct mq_attr attr;

    //1. 消息队列属性设置
    attr.mq_maxmsg = 16;
    attr.mq_msgsize = sizeof(data_obj);

    //2. 创建一个消息队列
    mqd_t mq_id = mq_open("/mq_test", O_CREAT | O_RDWR, 0666, &attr);
    if(mq_id == -1){
        printf("mq_open error(%d)\n",errno);
        exit(1);
    }

    int count = 10;
    while(count--){
        data_obj.count = count;
        sprintf((char *)data_obj.buff,"hello %02d\n",count);
        //3. 消息对了发送
        int res = mq_send(mq_id, (char *)&data_obj, sizeof(data_obj), 0);
        if(res == 0){//send true
            printf("mq_send %d\n",count);
            sleep(1);
        }
        else{
            int errno_is = errno;
            printf("mq_send error %d\n",errno_is);
            if(errno_is == EINTR){//信号中断
            }
            else if(errno_is == EAGAIN){队列已满
            }
        }
    }

    //4. 关闭消息队列
    if(mq_close(mq_id) == -1){
        printf("mq_close error(%d)\n",errno);
        exit(1);
    }

    exit(0);
}

//消息队列读进程
void process_read(void)
{
    struct shm_data data_obj;

    //1. 打开消息队列
    mqd_t mq_id = mq_open("/mq_test", O_RDONLY, 0, NULL);
    if(mq_id == -1){
        printf("mq_open read error(%d)\n",errno);
        exit(1);
    }

    //2. 获取消息队列相关属性
    struct mq_attr attr;
    if(mq_getattr(mq_id, &attr) == -1){
        printf("mq_getattr error(%d)\n",errno);
        exit(1);
    }
    printf("attr->mq_msgsize(%ld)\n",attr.mq_msgsize);
    printf("attr->mq_maxmsg(%ld)\n",attr.mq_maxmsg);
    printf("attr->mq_curmsgs(%ld)\n",attr.mq_curmsgs);

    while(1){
        //3. 等待接收
        ssize_t num = mq_receive(mq_id, (char *)&data_obj, sizeof(data_obj), NULL);
        if(num >= 0){//receive true
            printf("mq_receive:count: %d,%s",data_obj.count, data_obj.buff);
            if(data_obj.count == 0){
                break;
            }
        }
        else{
            int errno_is = errno;
            if(errno_is == EINTR){//信号中断
                printf("queue interrupt\n");
            }
            else{
                printf("mq_receive error %d:%s\n",errno_is,strerror(errno_is));
                break;
            }
        }
    }

    //4. 关闭消息队列
    if(mq_close(mq_id) == -1){
        printf("mq_close error(%d)\n",errno);
        exit(1);
    }

    //5. 删除消息队列
    if(mq_unlink("/mq_test") == -1){
        printf("mq_unlink error(%d)\n",errno);
        exit(1);
    }

    exit(0);
}

void main(void)
{
    pid_t pid = fork();
    if(pid == 0){//子进程
        printf("child process runing(%d)\n",getpid());
        sleep(2);
        process_read();
    }
    else{//父进程
        printf("parent process runing(%d)\n",getpid());
        process_write();
    }
}

关于技术交流

此处后的文字已经和题目内容无关,可以不看。
qq群:825695030
微信公众号:嵌入式的日常
如果上面的文章对你有用,欢迎打赏、点赞、评论。二维码

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

theboynoName

感谢鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值