UNIX 系统消息队列机制及应用

Posted 邱洋inCloud

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UNIX 系统消息队列机制及应用相关的知识,希望对你有一定的参考价值。

消息队列简介

     在传统的单任务操作系统中 , 程序设计的对象一经运行 , 程序就将独占整个主机资源, 程序实体之间的不同模块完全是通过全局变量、函数调用时的参数返回值来进行通信的。

    UNIX 操作系统是一个分时的多任务操作系统 , 程序运行后都将成为一个独立的实体——进程 , 进程间的通信不仅包括其内部通信 , 还包括进程间的通信。 UNIX System V 中提供了一系列的进程通信机构 , IPC 机构 , 消息队列就是其中之一。在 UNIX 系统中 , 所有的消息都放在系统内核当中 , 并且它们都有一个相应的消息队列标识符。进程可读写任意队列中特定的消息 , 其次序是消息到达的次序 , 核心负责维护这一适当的次序 , 而且在同一消息队列中 , 不同的进程可分别读出各自需要的消息 , 在其它进程向消息队列写入消息之前 , 进程可一直读取消息而不必等待消息到达队列。

     每一个位于消息队列中的消息都包括如下内容 :

    1. 长整数类型

    : 定义消息类型

    2. 消息的数据长度 : 定义数据长度

    3. 数据

    : 具体内容

     系统内核为消息队列维持如下数据结构信息 , 其定义包含在 <msg.h> 头文件中 :

    struct msqid_ds{

    struct ipc_perm msg_perms; /*operation permission struct*/

    struct msg *msg_first;

     /*ptr to first message on q*/

    struct msg *msg_last;

    /*ptr to last message on q*/

    ushort

    msg_cbytes;

    /*current num bytes on q*/

    ushort

    msg_qnum;

    /*no.message on q*/

    ushort

    msg_qbuyes;

    /*max no.bytes for q*/

    ushort

    msg_lspid;

     /*pid of last megsnd*/

    ushort

    msg_lrpid;

     /*pid of last msgrcv*/

    time_t

    msg_stime;

     /*last msgsnd time*/

    time_t

    msg_rtime;

     /*last msgrcv time*/

    time_t

    msg_ctime;

     /*last change time*/

     类型 ushort time_t 与系统实现有关 , 它们包含在头文件 <types.h> 中定义 ;ipc_perm 结构包含了对应消息队列的主人和存取权限 ; 结构 msg 被内核用来把某一队列上的消息链接为队列。

     1 是一个消息队列的示意图。

     系统调用格式及说明

     有关消息队列的系统调用均需下列头文件 :

    #include <sys/types>

    #include <sys/ipc.h>

    #include <sys/msg.h>

    1.msgget 调用

     该调用用来创建一个新的消息队列或访问一个已经存在的消息队列。调用格式如下

    int msg_qid,msgflag;

    key_t key;

    msg_qid=msgget(key,msgflag)

     其中 : 参数 key 是标识消息队列的键值 , 键的实际数据类型由与实现有关的类型 key_t决定 , 如果调用成功 , 就建立一新的消息队列 , 或使一个已存在的消息队列能被访问 , 返回值 msg_qid 内含有一个消息队列的标识符。

     参数 msgflag 与两个符号常数有关 , 它们在 <ipc.h> 中定义 , 可单独使用 , 也可按位或使用 :

    IPC_CREAT: 在执行 msgget , 在给出键值而未有队列时就建立一新队列 , 如消息队列已经存在 , 则不覆盖。没设置 IPC_CREAT , 当新队列存在时 ,msgget 就返回该队列的标识符。

    IPC_EXCL: 如该标志和 IPC_CTREAT 都被设置 , 在给出的键位已对应一个已存在的消息队列时 ,msgget 将会失败。

    2.msgsnd 调用

     msgsnd 打开一个消息队列后 , 就可调用 msgsnd 向消息队列发送消息 , 调用格式如下

    int msg_qid,size,flags,len;

    struct my_msg {

    long mtype;

    char mtext[maxvalue];

    }message;

    len=msgshd(msg_qid,&message,size,flags);

    msg_qid: 为消息发送至的队列 , 是通过 msgget 得到的。

    message: 为指向包含消息本身的结构 , 这种结构是用户自己定义的 , 其中的长整型 mtype 很重要 , 我们可用它来对消息进行分类。该域的每种可能的值都代表不同类型的消息, 正是通过该域 , 进程可以从消息队列中接收自己需要的消息 , 其值必须大于 0, 因为类型为0 的消息在系统调用 msgrcv 中特指一种类型。内核对消息内容不做任何解释。 mtext 域用来存放消息 ,MAXVALUE 可取任意值。

    size: 指被发送的实际长度 , 可取 0 至系统规定的消息的最大长度。

    flags: 可设置为 IPC_NOWAIT, 如没设置成 IPC_NOWAIT, 那么当系统没有足够系统资源来发送消息时 , 调用进程就会进入睡眠 ; 否则 , 调用就立即返回 , 返回值为 -1

    3.msgrcv 调用

     此调用用来从指定的消息队列中读出一个消息 , 调用格式如下 :

    int msg_qid,size,flags,len;

    long msg_type;

    struct my_msg {

    long mtype;

    char mtext[MAXVALUE];

    }message;len=msgrcv(msg_qid,&message,size,msg_type,flags);

    msg_qid: 为读出消息的队列标识符。

    len: 为接收的实际长度。

    message: 用来存放读出的消息。

    size: 指结构内能存放消息的最大长度。

    msg_type: 用来确定接收消息的类型。它是根据消息的 mtype 域来进行选择的 , 如果该参数为 0, 就读出队列中的第一个消息 ; 如参数为正 , 则读出 mtype 域为该值的第一个消息 ; 如果参数为负 , 就在 mtype 小于或等于 msg_type 绝对值的消息中选取 mtype 值最小的消息读出。

    flags: 含有控制信息 , 可设为 IPC_NOWAIT MSG_NOERROR, 或取它们按位或的结果。如设为 IPC_NOWAIT, 则当队列中没有合适的消息 , 调用就返回 , 否则就会进入睡眠。如设为 MSG_NOERROR, 那么当消息长度大于 size , 会把消息截短 , 否则调用失败。

    4.msgctl 的调用

     该调用有三个功能 , 获取消息队列的状态信息、改变消息队列的某些限制、从系统中删除一个队列 , 调用格式如下 :

    int msg_qid,command,len;

    struct msqid_ds msq_stat;

    msgctl (msg_qid,command,&msq_stat)

    msg_qid: 是一有效的消息队列标识符。

    msg_stat: 是一个 msgid_ds 结构的地址。

    command: 指出要完成的操作。

     有三种选择 , <ipc.h> 中定义 , 它们的意义如下 :

     · IPC_STAT: 把关于结构的状态信息放入 msg_stat

     · IPC_SET: 根据 msg_stst 中的信息 , 为消息队列设置控制变量值。

     · IPC_RMID: 从系统中删除队列 , 但只有超级用户或队列所有者才能使用。

     消息队列应用

     下面举例说明一个消息队列在 C/S 模式中的应用。 C/S 的一个重要特性就是非对等性, 多个客户机同时向一个服务器提出不同的请求 , 服务器接收并处理客户机的请求后 , 将结果返回给相应的客户机。在这里 , 我们利用两个消息队列、一个服务进程、若干客户进程来模拟客户 / 服务应用。由各客户进程将请求信息写入队列 1( 键值 0x16), 消息类型定为 10; 服务进程从队列 1 中读取类型为 10 的消息进行处理 , 并将处理结果放入消息队列 2( 键值0x17), 将类型置为原客户进程号 , 这样每个客户进程可根据其中的消息类型即进程号再从队列 2 中读出属于自己的结果 ( 2)

     具体操作时先启动服务进程 (server.c), 创建两个消息队列 , 键值分别为十六进制 16 17; 这时再启动若干客户进程 (client.c), 输入一任意字符串 , 这时服务进程则显示客户进程号和原字符串 , 客户进程则回显第一个字符被改为 "_" 的处理后的字符串和消息类型( 自身进程号 ) 。如输入字符 "q", 则删除原消息队列 , 同时退出。源程序清单附后 , 该程序在 UNIX3.2 4.2 环境下编译调试通过。

     这只是进程通信在机器内部的实现 , 要实现进程在网络机器间的通信还要借助 UNIX 网际系统的套接字来实现。

     源程序清单如下 :

     服务进程 :(server.c)

    #include <stdio.h>

    #include <sys/types.h>

    #include <sys/ipc.h>

    #include <sys/msg.h>

    #define KEY16 (key_t)16

    #define KEY17 (key_t)17

    #include (ctype.h)

    int i,msgid1,msgid2,val;

    char *s

    struct{

    long mtype;

    short stype;

    char mtext[40];

    }buf;

    main()

    {

    cr_q(); /* 创建消息队列 */

    /* 读消息队列 1, 消息队列类型为 10, 等待客户请求 */

    while   (msgrcv(msgid1,&buf,42,(long)10, IPC_NOWAIT)>=0)

    {printf("/n text from client is:[%s]",buf.mtext);

     printf ("client pid is <%d> /n",buf.stype);

     process();/* 将处理结果写入消息队列 2*/

    }

    }

    cr_q()

    {  msgid1=msgget(KEY16,IPC_CREAT|0666);

    if (msgid1<0) {perror("key16 msgget failed");exit(1);}

    msgid2=msgget(KEY17,IPC_CREAT|0666);

    if (msgid2<0) {perror("key17 msgget failed");exit(1);}

    }

    process()

    {

    buf.mtype=buf.stype;/* 原客户进程号 */

     buf.mtext[0]= _ ;

     if(msgsnd(msgid2,&buf,42,IPC_NOWAIT)<0)

     {perror("key17 msgsnd failed");exit(1);}

    }

     客户进程 :(client.c)

    #include <stdio.h>

    #include <sys/types.h>

    #include <sys/ipc.h>

    #include <sys/msg.h>

    define KEY16 (key_t)16

    define KEY17 (key_t)17

    include <ctype.h>

    int msgid1,msgid2;

    struct{

    long mtype;

    short stype;

    char mtext[40];

    }buf;

    char s[41];

    int clean()

    main()

    {

    get_q();/* 取消队列标志符 */

    while (1)

    {

    printf ("/n @ input mtext:");/* 输入字符串 */

    scanf("%s",s);

    if ((strcmp(s,"q")==0)) break;

    strcpy(buf.mtext,s);

    buf.mtype=10; /* 消息类型置为 10*/

    buf.stype=getpid();/* 客户进程号 */

    /* 写消息队列 1, 向服务进程提出请求 */

    if (msgsnd(msgid1,&buf,40, IPC_NOWAIT)<0)

    {perror("key16 msgsnd failed");exit(1);}

    /* 读消息队列 2, 类型为自身进程号 , 接收处理结果 */

    if (msgrcv(msgid2,&buf,42,getpid(), IPC_NOWAIT)<0)

     {perror("key17 msgrcv failed");exit(1);}

    printf("/n the answer from server is:[%s]",buf.mtext);

     printf("mtype is:[%d]",buf.mtype);

     }

    clean();/* 删除消息队列 */

    }

    clean()

    {

    if(msgct1(msgid1,IPC_RMID,(struct msgid*)NULL)<0)

    {perror("key16 msgct1 failed");exit(1);}

    if(msgct1(msgid2,IPC_RMID,(struct msgid*)NULL<0)

    {perror("key17 msgct1 failed");exit(1);}

    printf("msg queue removed/n");

    exit(0);

    }

    get_q()

    {

    msgid1=msgget(KEY16,0);

    if (msgid1<0) {perror("key16 msgget failed");exit(1);}

    msgid2=msgget(KEY17,0);

    if (msgid2<0) {perror("key17 msgget failed");exit(1);}

    }

以上是关于UNIX 系统消息队列机制及应用的主要内容,如果未能解决你的问题,请参考以下文章

Windows消息队列及C++应用

Windows消息队列及C++应用

8.windows消息机制消息队列

浅谈消息队列及常见的消息中间件

浅谈消息队列及常见的消息中间件技术

PHP消息队列实现及应用