IPC之Posix消息队列详解


基本概念:

    消息队列可认为是一个消息链表。有足够写权限的线程可往队列中放置消息,有足够读权限的线程可从队列中取走消息,每个消息都是一个记录(非字节流式,也就是不需要自定义边界),它由发送者赋予一个优先级。在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。

    一个进程可以往某个队列写入一些消息,然后终止,再让另外一个进程在以后某个时刻读出这些消息。消息队列具有随内核的持续性,也就是进程关闭后,消息队列依然存在。


消息队列的标准:

    消息队列分为两大标准,Posix消息队列和System V消息队列,此文只讲Posix消息队列。


此文的环境:

例子的运行系统:Ubuntu 14

运行前需要执行的命令(为什么需要执行下列命令,请查阅man 7 mq_overview):

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 filesystem can be mounted (by  the  superuser)
       using the following commands:


           # mkdir /dev/mqueue
           # mount -t mqueue none /dev/mqueue


       The sticky bit is automatically enabled on the mount directory.

创建消息队列:

MQ_OPEN(3)                 Linux Programmer's Manual                MQ_OPEN(3)

NAME
       mq_open - open a message queue

SYNOPSIS
       #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);

       Link with -lrt.
返回:若成功则为消息队列描述符,若出错则为-1


关闭消息队列:

MQ_CLOSE(3)                Linux Programmer's Manual               MQ_CLOSE(3)

NAME
       mq_close - close a message queue descriptor

SYNOPSIS
       #include <mqueue.h>

       int mq_close(mqd_t mqdes);

       Link with -lrt.
返回:若成功则为0,若出错则为-1

删除消息队列:

MQ_UNLINK(3)               Linux Programmer's Manual              MQ_UNLINK(3)

NAME
       mq_unlink - remove a message queue

SYNOPSIS
       #include <mqueue.h>

       int mq_unlink(const char *name);

       Link with -lrt.
返回:若成功则为0,若出错则为-1

获取/设置消息队列属性:

MQ_GETATTR(3)              Linux Programmer's Manual             MQ_GETATTR(3)

NAME
       mq_getattr, mq_setattr - get/set message queue attributes

SYNOPSIS
       #include <mqueue.h>

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

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

       Link with -lrt.
均返回:若成功则为0,若出错则为-1

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 */
};

a)mq_setattr函数只允许设置mq_attr结构的mq_flags成员,其它三个成员被忽略。

b)指向某个mq_attr结构的指针可作为mq_open的第四个参数传递,每个队列的最大消息数和每个消息的最大字节数只能在创建队列时设置,而且这两者必须同时指定。

c)消息队列中的当前消息数则只能获取不能设置。

例子1,创建并设置消息队列的属性,再删除消息队列。

#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    struct mq_attr attr;
    attr.mq_maxmsg = 24; /* 设置消息队列的最大消息个数 */
    attr.mq_msgsize = 8192; /* 设置每个消息的最大字节数 */

    char *name = "/temp.mq"; /* linux必须是/filename这种格式,不能出现二级目录 */
    mqd_t mqd = mq_open(name, O_RDWR | O_CREAT | O_EXCL, 0777, &attr);
    if (mqd == -1) {
        perror("create failed");
        mqd = mq_open(name, O_RDWR);
        if (mqd == -1) {
            perror("open failed");
            exit(EXIT_FAILURE);
        }
    }
    printf("mq_open %s success\n", name);

    /* 打开成功,获取当前属性 */
    mq_getattr(mqd, &attr);
    printf("max msg = %ld, max bytes = %ld, currently = %ld\n",
            attr.mq_maxmsg, attr.mq_msgsize, attr.mq_curmsgs);

    /* 关闭 */
    if (mq_close(mqd) != -1)
    	printf("mq_close %s success\n", name);

    /* 删除 */
    sleep(30); /* 观察创建情况 */
    if (mq_unlink(name) != -1)
        printf("mq_unlink %s success\n", name);

    return 0;
}

编译运行:



发送消息:

MQ_SEND(3)                 Linux Programmer's Manual                MQ_SEND(3)

NAME
       mq_send, mq_timedsend - send a message to a message queue

SYNOPSIS
       #include <mqueue.h>

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

       #include <time.h>
       #include <mqueue.h>

       int mq_timedsend(mqd_t mqdes, const char *msg_ptr,
                     size_t msg_len, unsigned msg_prio,
                     const struct timespec *abs_timeout);

       Link with -lrt.
返回:若成功则为0,若出错则为-1

mq_send的prio参数是待发送消息的优先级,其值必须小于MQ_PRIO_MAX。如果应用不必使用优先级不同的消息,那就给mq_send指定值为0的优先级,给mq_reveive指定一个空指针作为其最后一个参数。


接收消息:

MQ_RECEIVE(3)              Linux Programmer's Manual             MQ_RECEIVE(3)

NAME
       mq_receive, mq_timedreceive - receive a message from a message queue

SYNOPSIS
       #include <mqueue.h>

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

       #include <time.h>
       #include <mqueue.h>

       ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,
                          size_t msg_len, unsigned *msg_prio,
                          const struct timespec *abs_timeout);

       Link with -lrt.
返回:若成功则为消息中接字数,若出错则为-1


mq_receive总是返回所指定队列中最高优先级的最早消息,而且该优先级能随该消息的内容及其长度一同返回。

mq_receive的len参数的值不能小于能加到所指定队里中的消息的最大大小(该队列mq_attr结构的mq_msgsize成员)。要是len小于该值,mq_reveive就立即返回EMSGSIZE错误。这意味着使用Posix消息队列的大多数应用程序必须在打开某个队列后调用mq_getattr确定最大消息大小,然后分配一个或多个那样大小的读缓冲区。


例子2,发送3个优先级不同的消息并接收。

发送程序mqsend.c:

#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    struct mq_attr attr;
    attr.mq_maxmsg = 24; /* 设置消息队列的最大消息个数 */
    attr.mq_msgsize = 8192; /* 设置每个消息的最大字节数 */

    char *name = "/temp.mq"; /* linux必须是/filename这种格式,不能出现二级目录 */
    mqd_t mqd = mq_open(name, O_RDWR | O_CREAT | O_EXCL, 0777, &attr);
    if (mqd == -1) {
        perror("create failed");
        mqd = mq_open(name, O_RDWR);
        if (mqd == -1) {
            perror("open failed");
            exit(EXIT_FAILURE);
        }
    }
    printf("mq_open %s success\n", name);

    /* 打开成功,获取当前属性 */
    mq_getattr(mqd, &attr);
    printf("max msg = %ld, max bytes = %ld, currently = %ld\n",
            attr.mq_maxmsg, attr.mq_msgsize, attr.mq_curmsgs);

    char *msg_ptr1 = "hello world1";
    char *msg_ptr2 = "hello world2";
    char *msg_ptr3 = "hello world3";
    size_t msg_len1 = strlen(msg_ptr1);
    size_t msg_len2 = strlen(msg_ptr2);
    size_t msg_len3 = strlen(msg_ptr3);

    /* 先发送优先级低的 */
    mq_send(mqd, msg_ptr1, msg_len1, 1);
    mq_send(mqd, msg_ptr2, msg_len2, 2);
    mq_send(mqd, msg_ptr3, msg_len3, 3);

    printf("mq_send success\n");
    if (mq_close(mqd) != -1)
        printf("mq_close %s success\n", name);

    return 0;
}

接收程序mqreceive.c:

#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char *name = "/temp.mq"; /* linux必须是/filename这种格式,不能出现二级目录 */
    mqd_t mqd = mq_open(name, O_RDWR);
    if (mqd == -1) {
        perror("open failed");
        exit(EXIT_FAILURE);
    }
    printf("mq_open %s success\n", name);

    struct mq_attr attr;
    if (mq_getattr(mqd, &attr) == -1) {
        perror("mq_getattr failed");
        exit(EXIT_FAILURE);
    }

    char *msg_ptr;
    size_t msg_len = attr.mq_msgsize;
    unsigned msg_prio;
    msg_ptr = (char *)malloc(msg_len);

    while (1) {
        bzero(msg_ptr, msg_len);
        int res = mq_receive(mqd, msg_ptr, msg_len, &msg_prio);
        if (res != -1) {
            printf("msg is:%s, msg_prio:%d\n", msg_ptr, msg_prio);
        } else {
            perror("mq_receive failed");
            break;
        }
    }

    if (mq_close(mqd) != -1)
        printf("mq_close %s success\n", name);

    return 0;
}

编译运行:



消息队列限制:

我们已经遇到任意给定队列的两个限制,它们都是在创建该队列时建立的(测试中发现mq_getattr与ulimit -q的结果并不一致):

mq_maxmsg ->队列中的最大消息数

mq_msgsize ->给定消息的最大字节数

消息队列的实现定义了另外两个限制(调用sysconf函数获取):

MQ_OPEN_MAX -> 一个进程能够同时拥有的打开着消息队列的最大数目

MQ_PRIO_MAX -> 任意消息的最大优先级加1


测试代码与运行结果:

#include <unistd.h>
#include <stdio.h>

// man 7 mq_overview

int main()
{
    long mq_open_max = sysconf(_SC_MQ_OPEN_MAX);
    long mq_prio_max = sysconf(_SC_MQ_PRIO_MAX);
    printf("mq_open_max:%ld\n", mq_open_max);
    printf("mq_prio_max:%ld\n", mq_prio_max);
}



高级:

Posix消息队列允许异步事件通知,以告知合适有一个消息放置到某个空消息队列中。这种通知有两种方式可供选择:

a)产生一个信号

b)创建一个线程来执行一个指定的函数。

这种通知通过调用mq_notify建立。

MQ_NOTIFY(3)               Linux Programmer's Manual              MQ_NOTIFY(3)

NAME
       mq_notify - register for notification when a message is available

SYNOPSIS
       #include <mqueue.h>

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

       Link with -lrt.

这里不进行深入展开。



参考:《unix网络编程》·卷2

End;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值