linux进程通信———Posix消息队列简介及基础库函数

linux进程通信———Posix消息队列简介及基础库函数

引言:消息队列可认为是一个消息链表,有足够写权限的线程可向队列中放置消息,有足够读权限的线程可从队列中取走消息。本篇笔记将简要介绍Posix消息队列特性、基本库函数调用。


    • linux进程通信———Posix消息队列简介及基础库函数
      • 一、Posix消息队列简介
        • 1.1、与管道、FIFO差异
        • 1.2、Posix消息队列与System V消息队列的差异
        • 1.3、消息队列的基本属性:
      • 二、基本库函数
        • 2.1、mq_open函数:
        • 2.2、mq_close函数:
        • 2.3、mq_unlink函数:
        • 2.4、mq_getattr与mq_setattr函数:
        • 2.5、mq_send与mq_receive函数:
      • 三、代码测试实例
        • 3.1、创建一个消息队列
        • 3.2、获取、修改消息队列属性
        • 3.3、测试向队列中发送,并取走消息


一、Posix消息队列简介

1.1、与管道、FIFO差异

1)、存储特性:在某个进程往一个消息队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达,但是对管道和FIFO来讲,除非读出者已经存在,否则先有写入者是没有意义的。

管道的缓存大小为一个内存页,需要读写进程协同工作才能充分利用管道的“流通”特性,其本身存储信息能力微弱。但是消息队列是一个链表,可以动态扩展,有存储信息的能力,故其在写数据时,不需要某个进程等待消息到达。

2)、生命周期:管道和FIFO是随进程的持续性,即当一个管道或者FIFO的最后一次关闭发生时,仍在该管道或FIFO的数据将被丢弃。而消息队列具有随内核的持续性,直到内核自举或者显示删除该对象为止。一个进程可以向某个队列中写入一些消息,然后终止,再让另外一个进程在以后某个时刻读取这些消息。

3)、数据类型差异:管道和FIFO是字节流模型,没有消息边界,也没有与每个消息关联的类型,但消息队列数据存在类型。

linux进程通信———Posix消息队列简介及基础库函数_第1张图片
图1、消息队列与管道的差异

1.2、Posix消息队列与System V消息队列的差异

  本篇笔记介绍Posix消息队列,后面还会介绍System V消息队列,这两组函数间存在很多相似性,下面是二者的差别:

1)、读取差异:Posix消息队列的读总是返回最高优先级的最早消息,对System V消息队列的读可以返回任意指定优先级的消息
2)、写入差异:当向一个空队列放置消息时,Posix消息队列允许产生一个信号或启动一个线程,System V则不提供类似机制。

1.3、消息队列的基本属性:

1)、一个无符号整数优先级(Posix)或一个长整数类型(System V)
2)、消息的数据部分长度(可以为0)
3)、数据本身(如果长度大于0,则有数据)

linux进程通信———Posix消息队列简介及基础库函数_第2张图片
图2、含有三个消息的某个Posix消息队列的可能布局

  消息队列可认为是一个消息链表,该链表的头中含有当前队列的两个属性:mq_maxmsg为队列中允许的最大消息数、mq_msgsize为单个消息的容量上限。由于消息队列具有随内核的持续性,于是我们可以编写若干个小程序来使用它,以便深入理解它。


二、基本库函数

  Posix消息队列的基本操作函数包括mq_open、mq_close、mq_unlink、mq_getattr、mq_setattr、mq_send、mq_receive。当然mq是message queue消息队列的缩写,函数依次对应为创建或打开队列、关闭队列、接触链接、获取队列属性、设置队列属性、向队列放置消息、及从队列中取走一个消息。下面对它们依次介绍并附带实例测试。

2.1、mq_open函数:

1)、概述: mq_open()函数创建一个新的消息队列或打开一个已经存在的队列。

linux进程通信———Posix消息队列简介及基础库函数_第3张图片
图3、mq_open函数

2)、参数:

  • const char* name:用于指定队列的名字
  • oflag:是O_RDONLY、O_WRONLY、或O_RDWR之一,可能按位或上O_CREAT、O_EXCL或O_NONBLOCK。
  • mode:用于指定权限位,包括S_IRUSR、S_IWUSR等。
  • attr: attr参数用于给新队列指定属性,如果其为空指针,则使用默认属性。

当实际操作是创建一个新队列,即已指定O_CREAT标志且所请求的消息队列尚未存在,则mode和attr参数是需要的。

3)、返回值: mq_open参数的返回值称为消息队列描述符(message queue descriptor),这个值用于其余7个消息队列函数的第一个参数。若其失败,则返回-1.

2.2、mq_close函数:

1)、概述:已打开的消息队列是由mq_close关闭的,其功能与我们关闭一个word文件类似:调用进程可以不再使用该描述符,但其消息队列并不从系统中删除。当一个进程终止时,它所有打开的消息队列都关闭,就像调用mq_close一样。

linux进程通信———Posix消息队列简介及基础库函数_第4张图片
图4、mq_close函数

2)、参数:

  • mqd_t mqdes:所要关闭消息队列的描述符

3)、返回值:成功关闭返回0,失败返回-1;

2.3、mq_unlink函数:

1)、概述:要从系统中删除用作mq_open第一个参数的某个name,必须调用mq_unlink。当一个消息队列的引用计数仍大于0时,其name就可以删除,但是该队列的析构(拆除队列),要在其引用计数变为0才进行。

我的理解是仅打开该消息队列的进程,可以执行相应的读取写入操作,其他进程已经找不到该消息队列,无法建立连接了。

linux进程通信———Posix消息队列简介及基础库函数_第5张图片
图5、mq_unlink函数

2)、参数:

  • mqd_t mqdes:所要删除名字的消息队列描述符

3)、返回值:成功返回0,失败返回-1;

2.4、mq_getattr与mq_setattr函数:

1)、概述:每个消息队列有四个属性,mq_getattr返回这些属性,mq_setattr设置其中某个属性。其中mq_flags在mq_open创建或打开时,已经初始化,唯一能改变的即设置为O_NONBLOCK。其他项不会改变。

linux进程通信———Posix消息队列简介及基础库函数_第6张图片
图6、mq_getattr与mq_setattr

2)、参数:

  • mqd_t mqdes:所要获取属性或修改属性的消息队列描述符
  • struct mq_attr *attr:描述消息队列属性的结构体指针

3)、返回值:成功返回0,失败返回1

2.5、mq_send与mq_receive函数:

1)、概述:这两个函数分别用于向一个队列中放置一个消息,和从一个队列中取走一个消息。每个消息有一个优先级,它是一个小于MQ_PRIO_MAX的无符号整数。这两个函数的前三个参数与write和read的前三个参数类似。mq_send函数的prio参数是待发送消息的优先级,其值必须小于MQ_PRIO_MAX。

linux进程通信———Posix消息队列简介及基础库函数_第7张图片
图7、mq_send函数

2)、mq_send与mq_timesend的区别
**区别:**mq_timesend是一个系统调用,mq_send是库函数。但是二者表现类似,只是mq_timesend附加了一个等待时间,若消息队列为满,且设置为非阻塞,则mq_timesend会阻塞至struct timespec制定的时间,然后返回。详情可以参考man手册。

linux进程通信———Posix消息队列简介及基础库函数_第8张图片
图8、mq_send与mq_timesend差异

3)、mq_receive函数:

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

linux进程通信———Posix消息队列简介及基础库函数_第9张图片
图9、mq_receive函数

三、代码测试实例

3.1、创建一个消息队列

/********************************************************
*内容:使用mq_open函数创建消息队列,其名字通过命令行参数指定
*时间:2018.04.02
*问题:1、编译过程提示 undefined reference to ‘mq_open’
       解决:gcc -o mqcreat mqcreat.c -lrt 
       2、找不到自己建立的消息队列:
       解决:mkdir /dev/mqueue
             mount -t mqueue none /dev/mqueue 
       3、队列名称的限定:仅可有一个/
*****************************************************/
#include
#include
#include
#include
#include

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IRUSR)

int main(int argc, char* argv[] )
{

    int flag = O_RDWR | O_CREAT;
    mqd_t mqd;

    //第一步、用户输入合法性检测:
    if (argc != 2) 
    {
        fprintf(stderr, "usage : %s\n", argv[0]);
        exit(1);
    }

    //第二步、创建消息队列
    mqd = mq_open(argv[1], flag, FILE_MODE, NULL);
    if(-1 != mqd)
    {
        printf("message queue creat success!\n");
        //mq_close(mqd);
    }
    else
    {
        printf("message queue creat fail!\n");
    }

    exit(0);

}

编译及执行结果:

linux进程通信———Posix消息队列简介及基础库函数_第10张图片
图10、编译及执行结果

已创建队列查看:

linux进程通信———Posix消息队列简介及基础库函数_第11张图片
图11、建立的消息队列

unlink删除一个消息队列:

linux进程通信———Posix消息队列简介及基础库函数_第12张图片
图12、unlink消息队列

3.2、获取、修改消息队列属性

/**************************************************
*内容:打开一个指定的消息队列,并且输出其属性
*时间:2018.04.02
*问题:注意mq_setattr给队列设置属性,仅能修改mq_flag标志位
       队列长度mq_maxmsg及单条消息上限mq_msgsize
       仅能创建时指定,当前消息数mq_curmsg只能获取不能指定
***************************************************/

#include
#include
#include
#include
#include

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IRUSR)

int main(int argc, char* argv[] )
{

    mqd_t mqd;
    struct mq_attr old_attr, new_addr;


    //第一步、用户输入合法性检测:
    if (argc != 2) 
    {
        fprintf(stderr, "usage : %s\n", argv[0]);
        exit(1);
    }

    //第二步、打开消息队列,获取并打印属性信息
    mqd = mq_open(argv[1], O_RDWR);
    if(-1 != mqd)
    {
        printf("message queue open success!\n");
        if(0 == mq_getattr(mqd, &old_attr) )
        {
            printf(" Intial: mq_flags = %ld. mq_maxmsg = %ld\n", old_attr.mq_flags, old_attr.mq_maxmsg);
        }
    }
    else
    {
        printf("message queue creat fail!\n");
    }

    //第三步、修改属性信息,并且再次打印:
    new_addr.mq_flags = O_NONBLOCK;
    new_addr.mq_maxmsg = 12;
    if(0 == mq_setattr(mqd,&new_addr, &old_attr))
    {
        printf("message queue setattr success.\n");
    }

    sleep(3);
    if(0 == mq_getattr(mqd, &old_attr))
    {
        printf(" New: mq_flags = %ld. mq_maxmsg = %ld\n", old_attr.mq_flags, old_attr.mq_maxmsg);
    }

    mq_close(mqd);
    exit(0);

}

执行结果:

linux进程通信———Posix消息队列简介及基础库函数_第13张图片
图13、获取并修改队列属性

结合代码及结果:确实mq_setattr给队列设置属性,仅能修改mq_flag标志位,队列长度mq_maxmsg及单条消息上限mq_msgsize,仅能创建时指定,当前消息数mq_curmsg只能获取不能指定。

3.3、测试向队列中发送,并取走消息

/*********************************************************
*内容:测试Posix消息队列中,消息的添加与读取
*逻辑:向队列中添加优先级不同的消息
*时间:2018.04.02
*问题:
**********************************************************/

#include
#include
#include
#include
#include

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IRUSR)

int main(int argc, char* argv[] )
{

    mqd_t mqd;
    struct mq_attr attr;
    char *ptr = "hello msssage queue";
    size_t len;
    unsigned prio;


    //第一步、用户输入合法性检测,读入参数
    if (argc != 4) 
    {
        fprintf(stderr, "usage : %s\n", argv[0]);
        exit(1);
    }
    len = atoi(argv[2]);
    prio = atoi(argv[3]);


    //第二步、打开消息队列
    mqd = mq_open(argv[1], O_RDWR);
    if(-1 != mqd)
    {
        printf("message queue open success!\n");
    }
    else
    {
        printf("message queue creat fail!\n");
    }

    //第三步、读取当前消息队列内消息数
    if(0 == mq_getattr(mqd, &attr) )
    {
        printf(" Intial: mq_curmsg = %ld.\n", attr.mq_curmsgs);
    }

    //第四步、添加一条消息
    if(0 == mq_send(mqd, ptr, len, prio));
    {
        printf("mq_send success!\n");
    }   

    //第五步、再次读取当前消息队列内消息数
    if(0 == mq_getattr(mqd, &attr) )
    {
        printf(" After mq_send: mq_curmsg = %ld.\n", attr.mq_curmsgs);
    }

    mq_close(mqd);
    exit(0);

}

发送消息测试结果:

linux进程通信———Posix消息队列简介及基础库函数_第14张图片
图14、发送消息测试结果

/*********************************************************
*内容:测试Posix消息队列中,消息的添加与读取
*逻辑:读取消息内容
*时间:2018.04.02
*问题:如果在读写代码,添加上getpid()打印不同进程ID会更直观
**********************************************************/

#include
#include
#include
#include
#include
#include

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IRUSR)

int main(int argc, char* argv[] )
{

    mqd_t mqd;
    struct mq_attr attr;
    void *buff;
    size_t len;
    unsigned prio;


    //第一步、用户输入合法性检测,读入参数
    if (argc != 2) 
    {
        fprintf(stderr, "usage : %s\n", argv[0]);
        exit(1);
    }



    //第二步、打开消息队列
    mqd = mq_open(argv[1], O_RDWR);
    if(-1 != mqd)
    {
        printf("message queue open success!\n");
    }
    else
    {
        printf("message queue creat fail!\n");
    }

    //第三步、读取当前消息队列内消息数
    if(0 == mq_getattr(mqd, &attr) )
    {
        printf(" Intial: mq_curmsg = %ld.\n", attr.mq_curmsgs);
    }
    buff = malloc(attr.mq_msgsize);
    memset(buff, 0, sizeof(attr.mq_msgsize));


    //第四步、读取一条消息
    len = mq_receive(mqd, buff,attr.mq_msgsize , &prio);
    if( len > 0);
    {
        printf("read messages: %s\n", (char*)buff);
        printf("read %ld bytes, priorrity = %u\n", (long)len, prio);
    }   

    //第五步、再次读取当前消息队列内消息数
    if(0 == mq_getattr(mqd, &attr) )
    {
        printf(" After mq_receive: mq_curmsg = %ld.\n", attr.mq_curmsgs);
    }

    mq_close(mqd);
    exit(0);

}

执行结果:

linux进程通信———Posix消息队列简介及基础库函数_第15张图片
图15、消息发送与接收测试,交替执行


参考资料:
1、unix 网络编程卷II
2、linux man手册 Ubuntu 14.04

纠错与建议
邮箱:[email protected]


你可能感兴趣的:(进程/线程)