【linux】系统编程-2-消息队列


前言

4. 消息队列

4.1 概念

  • 消息队列
    • 消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法

4.2 对比

  • 消息队列与信号之间的对比
    • 信号承载的信息量少,而消息队列可以承载大量自定义的数据
  • 消息队列与管道之间的对比
    • 相同
      • 进程间通信都可以是不相关的进程
      • 都可以独立于发送和接收进程而存在
      • 在进程终止时,其内容并不会被删除
    • 不同
      • 在命名管道中,发送数据用 write(),接收数据用 read(), 则在消息队列中,发送数据用 msgsnd(),接收数据用 msgrcv() ,消息队列对每个数据都有一个最大长度的限制
      • 管道只能承载无格式字节流,消息队列提供有格式的字节流
      • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级,接收程序可以通过消息类型有选择地接收数据, 而不是像命名管道中那样,只能默认地接收
      • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的顺序接收,也可以按消息的类型接收

4.3 函数及使用流程

  • 使用流程:
    1. 使用 msgget() 来创建或打开消息队列
    2. 使用 msgsnd() 来发送消息到文末
    3. 使用 msgrcv() 来接收消息,可指定某一条消息
    4. 使用 msgctl() 来控制消息(具体往下看

4.3.1 msgget()

  • 使用 msgget() 来创建或打开消息队列
  • 通过命令 man 了解更多
  • 函数原型:int msgget(key_t key, int msgflg);
    • key:消息队列的关键字值,多个进程可以通过它访问同一个消息队列
    • msgflg:表示创建的消息队列的模式标志参数,主要有IPC_CREAT,IPC_EXCL和权限mode,如:
      • IPC_CREAT:没有关键字 key 的消息队列就新建一个,有就直接打开
      • IPC_CREAT | IPC_EXCL:消息队列不存在,则新建一个,如果消息队列存在,则报错
      • IPC_CREAT | 0666:(注:消息队列不在意执行权限)
    • 返回
      • 成功:返回消息队列标识值
      • 失败:返回-1
        • 返回的错误码,即是在变量 error
          • EACCES:消息队列存在,但进程没有访问权限
          • EEXIST:msgflg 同时指定了 IPC_CREAT和IPC_EXCL,但是消息队列已经存在
          • ENOENT:消息队列不存在,且没有指定 IPC_CREAT 标志
          • ENOMEM:内存不足
          • ENOSPC:消息队列个数达到系统的限制

4.3.2 msgsng()

  • 使用 msgsnd() 来发送消息到文末
  • 通过命令 man 了解更多
  • 函数原型:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    • msqid:消息队列标识符
    • msgp:发送给队列的消息。msgp可以是任何类型的结构体,但第一个字段必须为long类型, 即表明此发送消息的类型,msgrcv()函数则根据此接收消息
      /*msgp定义的参照格式*/
      struct s_msg{
      long type;  /* 必须大于0,消息类型 */
      char mtext[1];  /* 消息正文,可以是其他任何类型 */
      } msgp;
      
    • msgsz:要发送消息的大小,不包含消息类型占用的4个字节
    • msgflg:
      • 0:当消息队列满时,msgsnd() 函数将会阻塞,直到消息能写进消息队列
      • IPC_NOWAIT:当消息队列已满的时候,msgsnd() 函数不等待立即返回
      • IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程
    • 返回
      • 成功:返回 0
      • 失败:返回 -1,错误原因存在于变量 error
        • EAGAIN:参数 msgflg 设为 IPC_NOWAIT,而消息队列已满
        • EIDRM:标识符为 msqid 的消息队列已被删除
        • EACCESS:无权限写入消息队列
        • EFAULT:参数 msgp 指向无效的内存地址
        • EINTR:队列已满而处于等待情况下被信号中断
        • EINVAL:无效的参数msqid、msgsz或参数消息类型type小于0
  • 解除阻塞的三个条件:
    1. 消息队列变为未满
    2. 消息队列被删除
    3. 调用 msgsnd() 的进程被信号中断

4.3.3 msgrcv()

  • 使用 msgrcv() 来发送消息到文末

  • 通过命令 man 了解更多

  • 函数原型:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

    • msqid:消息队列标识符
    • msgp:存放消息的结构体,结构体类型要与msgsnd()函数发送的类型相同
    • msgsz:要接收消息的大小,不包含消息类型占用的4个字节
    • msgtyp:
      • > 0:表示接收类型等于msgtyp的第一个消息
      • = 0:表示接收第一个消息
      • < 0:表示接收类型等于或者小于msgtyp绝对值的第一个消息
    • msgflg:
      • 0:阻塞式接收消息,没有该类型的消息 msgrcv 函数一直阻塞等待
      • IPC_NOWAIT:若在消息队列中并没有相应类型的消息可以接收,则函数立即返回,此时错误码为ENOMSG
      • IPC_EXCEPT:与 msgtype 配合使用返回队列中第一个类型不为 msgtype 的消息
      • IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的 size 字节,则把该消息截断,截断部分将被丢弃
    • 返回:
      • 成功:返回接收到的消息长度
      • 失败:返回 -1,错误原因存在于变量 error 中:
        • E2BIG:消息数据长度大于msgsz而msgflag没有设置IPC_NOERROR
        • EIDRM:消息队列已被删除
        • EACCESS:无权限读取该消息队列
        • EFAULT:参数msgp指向无效的内存地址
        • ENOMSG:参数msgflg设为IPC_NOWAIT,而消息队列中无消息可读
        • EINTR:等待读取队列内的消息情况下被信号中断
  • 解除阻塞的三个条件:

    1. 消息队列中已有符合条件的消息
    2. 消息队列被删除
    3. 调用 msgrcv() 的进程被信号中断

4.3.4 msgctl()

  • 使用 msgctl() 来设置或者获取消息队列的相关属性等等
  • 通过命令 man 了解更多
  • 函数原型:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    • msqid:消息队列标识符
    • cmd:操作命令
      • IPC_STAT:获取该 MSG 信息并存到结构体 msqid_ds 类型的 buf 中
      • IPC_SET:设置消息队列的属性,属性为 msqid_ds(msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes)
      • IPC_RMID:立即删除该 MSG,并且唤醒所有阻塞在该 MSG上的进程,同时忽略第三个参数
      • IPC_INFO:获得关于当前系统中 MSG 的限制值信息
      • MSG_INFO:获得关于当前系统中 MSG 的相关资源消耗信息
      • MSG_STAT:同 IPC_STAT,但 msgid 为该消息队列在内核中记录所有消息队列信息的数组的下标, 因此通过迭代所有的下标可以获得系统中所有消息队列的相关信息
    • buf:相关信息结构体缓冲区
    • 返回值
      • 成功:返回 0
      • 失败:返回 -1,错误原因存在 error 中:
        • EACCESS:参数cmd为IPC_STAT,却无权限读取该消息队列
        • EFAULT:参数buf指向无效的内存地址
        • EIDRM:标识符为msqid的消息队列已被删除
        • EINVAL:无效的参数cmd或msqid
        • EPERM:参数cmd为IPC_SET或IPC_RMID,却无执行权限

4.4 例程

  • 发送进程
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define BUFFER_SIZE 512
/*定义消息结构体*/
struct message{
    long msg_type;
    char msg_text[BUFFER_SIZE];
};
int main()
{
    int qid;
    struct message msg;

    /*1. 创建消息队列*/    
    if ((qid = msgget((key_t)1234, IPC_CREAT|0666)) == -1)
    {
        perror("msgget\n");
        exit(1);
    }

    /*打印标识符*/
    printf("Open queue %d\n",qid);

    while(1)
    {
        printf("Enter some message to the queue:");
        if ((fgets(msg.msg_text, BUFFER_SIZE, stdin)) == NULL)
        {
            printf("\nGet message end.\n");
            exit(1);
        }
        /*赋值消息类型*/
        msg.msg_type = getpid();        
        /*2. 添加消息到消息队列*/        
        if ((msgsnd(qid, &msg, strlen(msg.msg_text), 0)) < 0)
        {
            perror("\nSend message error.\n");
            exit(1);
        }
        else
        {
            printf("Send message.\n");
        }        
        if (strncmp(msg.msg_text, "quit", 4) == 0)
        {
            printf("\nQuit get message.\n");
            break;
        }
    }
    exit(0);
}
  • 接收进程
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define BUFFER_SIZE 512
/*定义消息结构体,与发送进程中的一样*/
struct message{
    long msg_type;
    char msg_text[BUFFER_SIZE];
};
int main()
{
    int qid;
    struct message msg;

    /*创建消息队列,键值和发送进程的一样*/    
    if ((qid = msgget((key_t)1234, IPC_CREAT|0666)) == -1)    
    {
        perror("msgget");
        exit(1);
    }
    /*打印标识符*/
    printf("Open queue %d\n", qid);
    do
    {
        /*读取消息队列*/
        memset(msg.msg_text, 0, BUFFER_SIZE);
        if (msgrcv(qid, (void*)&msg, BUFFER_SIZE, 0, 0) < 0)        
        {
            perror("msgrcv");
            exit(1);
        }
        printf("The message from process %ld : %s", msg.msg_type, msg.msg_text);
    } while(strncmp(msg.msg_text, "quit", 4));

    /*从系统内核中删除消息队列*/    
    if ((msgctl(qid, IPC_RMID, NULL)) < 0)    
    {
        perror("msgctl");
        exit(1);
    }
    else
    {
        printf("Delete msg qid: %d.\n", qid);
    }
    exit(0);
}

参考:

* 野火
posted @ 2020-12-29 08:48  李柱明  阅读(464)  评论(0编辑  收藏  举报