消息队列(十四)

一 简介

  消息队列是链表队列,它通过内核提供一个struct msqid_ds *msgque[MSGMNI]向量维护内核的一个消息队列列表,因此linux系统支持的最大消息队列数由msgque数组大小来决定,每一个msqid_ds表示一个消息队列,并通过msqid_ds.msg_firstmsqid_ds.msg_last维护一个先进先出的msg链表队列。
  当发送一个消息到该消息队列时,把发送的消息构造成一个msg结构对象,并添加到msqid_ds.msg_firstmsqid_ds.msg_last维护的链表队列,同样,接收消息的时候也是从msg链表队列尾部查找到一个msg_type匹配的msg节点,从链表队列中删除该msg节点,并修改msqid_ds结构对象的数据。Linux的消息队列(queue),它有消息队列标识符(queue ID)。msgget创建一个新队列或打开一个存在的队列;msgsnd向队列末端添加一条新消息;msgrcv从队列中取消息,取消息是不一定遵循先进先出的,也可以按消息的类型字段取消息。

二 消息队列的数据结构

msg.c中包含的函数模块。
  有关进程间通信资源的属性:
  键(key):一个由用户提供的整数,用来标志某个消息。
  创建者(creator):创建这个消息的进程的用户ID(UID)和组ID(GID)。
  所有者(owner):消息所有者的UID和GID。资源创建时,资源的创建者就是资源的所有者。资源的创建者进程、当前的所有者进程和超级用户具有改变资源所有者的权力。

1 struct ipc_perm

struct ipc_perm{   
    key_t key;   //整型,0表示private,非0表示public
	ushort uid;   //资源拥有者的有效标识
	ushort gid;   //资源拥有者所在组的有效标识
	ushort cuid;   //资源创建者的有效标识
	ushort cgid;   //资源创建者所在组的有效标识
	ushort mode;  //访问模式
	ushort  seq;  //序列号,计算标识符
};

  系统在创建消息队列的同时设定了访问权限,并返回一个标识。进程通信时必须先传递该标识,待函数ipcperms()确认权限后才可以访问通信资源。访问权限由ipc_perm结构描述。通过key可以得到引用标识,从而访问通信资源。Key为public,则任何进程都可以通过key得到引用标识。

2 msg结构用来存放消息的有关信息

struct msg{   
	struct msg *msg_next;   //消息队列中的下一个
    long msg_type;         //消息的类型
    char *msg_spot;        //存放消息内容的地址
    time_t msg_time;       //消息发送的时间
    short msg_ts;          //消息的长度
    message;                      /*消息体*/
};

3 存放消息的信息

struct msgbuf{  
	long  mtype;           //消息的类型
    char  mtext[1];         //消息的内容
};

4 每一个msqid_ds结构代表一个消息队列, 是进程读写的信息的存储空间

static struct msqid_ds *msgque[MSGMNI]

  定义了一个消息队列数组msgque, 数组的元素类指向msqid_ds 结构的指针。消息在队列中是按到来的顺序维护。进程读消息时,这些消息按FIFO从队列中移去。

struct msqid_ds{  
	struct ipc_perm msg_perm;    //权限
	struct msg *msg_first;       //指向消息队列的第一条消息 
	struct msg *msg_last;        //指向消息队列的最后一条消息
	time_t msg_stime;           // 最后发送时间 
	time_t msg_rtime;           //最后接收时间
	time_t msg_ctime;           //最后修改时间
	struct wait_queue *wwait;    //写消息进程的等待队列
	struct wait_queue *rwait;    //读消息进程的等待队列
	ushort msg_cbytes;          //队列中消息的字节数
	ushort msg_qnum;           //队列中的消息数
	ushort msg_qbytes;          //队列中消息的最大字节数
	ushort msg_lspid;        // 最后一个发送消息的进程的标识号
	ushort msg_lrpid;        //最后一个接收消息的进程的标识号
}; 

在这里插入图片描述
在这里插入图片描述

三 消息队列的使用

一 消息队列Key的获取:

  在程序中若要使用消息队列,必须要能知道消息队列key,因为应用进程无法直接访问内核消息队列中的数据结构,因此需要一个消息队列的标识,让应用进程知道当前操作的是哪个消息队列,同时也要保证每个消息队列key值的唯一性。

  1. 通过ftok函数获取
key_t key;
key=ftok(".","a")

该函数通过一个路径名称映射出一个消息队列key(我的理解是使用路径映射的方式比较容易获取一个唯一的消息队列key)

  1. 直接定义key:
#define MSG_KEY      123456

自定义key的方式要注意避免消息队列的重复。

二 获取或者打开一个消息队列

  1. 使用说明
    qid=msgget(key_t key, int msgflag)
    –key: 消息队列key
    –msgflag:
    IPC_PRIVATE:创建一个该进程独占的消息队列,其它进程不能访问该消息队列;
    IPC_CREAT:若消息队列不存在,创建一个新的消息队列,若消息队列存在,返回存在的消息队列;
    IPC_CREAT | IPC_EXCL: IPC_EXCL标志本身没有多大意义,与IPC_CREAT一起使用,保证只创建新的消息队列,若对应key的消息队列已经存在,则返回错误;
    IPC_NOWAIT:小队列以非阻塞的方式获取(若不能获取,立即返回错误)。
  2. 函数原因
    1)如果key==IPC_PRIVATE,则申请一块内存,创建一个新的消息队列(数据结构msqid_ds),将其初始化后加入到msgque向量表中的某个空位置处,返回标示符。
    2)在msgque向量表中找键值为key的消息队列,如果没有找到,结果有二:
    msgflag表示不创建新的队列,则错误返回。
    msgflag表示要创建新的队列(IPC_CREAT),则创建新消息队列,创建过程如1)。
    3)如果在msgque向量表中找到了键值为key的消息队列,则有以下情况:
    如果msgflg表示一定要创建新的消息队列而且不允许有相同键值的队列存在,则错误返回。
    如果找到的队列是不能用的或已经损坏的队列,则错误返回。
    认证和存取权限检查,如果该队列不允许msgflg要求的存取,则错误返回。
    正常,返回队列的标识符。

三 发送一个消息到消息对列

  1. 使用说明:
    int msgsnd (int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg)
    ------msqid为消息队列的qid
    ------msgp对应消息内容结构体指针
    ------msgsz消息的大小即msgp指针指向的消息结构体的大小
    ------msgflg消息标志
    0:忽略该标志位,以阻塞的方式发送消息到消息队列
    IPC_NOWAIT:以非阻塞的方式发送消息,若消息队列满,函数立即返回。
    ------返回:
    0: 成功
    -1:非阻塞方式访问满消息队列返回
    EACCES:没有该消息队列写权限
    EFAULT:消息队列地址无法获取
    EIDRM:消息队列已经被删除
    EINTR:消息队列等待写入的时候被中断
    ENOMEM:内存不够
  2. 函数原理:
    1)计算id = (unsigned int) msqid % MSGMNI,然后根据id在linux系统消息队列向量msgque[MSGMNI]中查找对应的消息队列,并进行认证检查,合法性检查
    2)如果队列已满,以可中断等待状态(TASK_INTERRUPTIBLE)将当前进程挂起在wwait等待队列(发送消息等待队列)上(msgflag==0)。
    3)否则 根据msgbuf的大小申请一块空间,并在其上创建一个消息数据结构struct msg(内核空间),将消息缓冲区中的消息内容拷贝到该内存块中消息头的后面(从用户空间拷贝到内核空间)。
    4)将消息数据结构加入到消息队列的队尾,修改队列msqid_ds的相应参数。
    5)唤醒在该消息队列的rwait进程队列(读等待进程队列)上等待读的进程,并返回。

四 从消息队列接收一个消息到msgbuf*

  1. 使用说明
    int msgrcv (int msqid, struct msgbuf *msgp, size_t msgsz,long msgtyp, int msgflg)
    ----msqid为消息队列的qid
    ----msgp是接收到的消息将要存放的缓冲区
    ----msgsz是消息的大小
    ----msgtyp是期望接收的消息类型
    ----msgflg是标志
    0:表示忽略
    IPC_NOWAIT:如果消息队列为空,不阻塞等待,返回一个ENOMSG
    ----返回
    0:成功
    -1:消息长度大于msgsz
    EACCES:没有该消息队列读权限
    EFAULT:消息队列地址无法获取
    EIDRM:消息队列已经被删除
    EINTR:消息队列等待写入的时候被中断
    ENOMEM:内存不够
  2. 函数原理:
    1)计算id = (unsigned int) msqid % MSGMNI,然后根据id在msgque[MSGMNI]中查找对应的消息队列,并进行认证检查,合法性检查
    2)根据msgtyp搜索消息队列,情况有二:
    ----如果找不到所要的消息,则以可中断等待状态(TASK_INTERRUPTIBLE)将当前进程挂起在rwait等待队列上
    ----如果找到所要的消息,则将消息从队列中摘下,调整队列msqid_ds参数,唤醒该消息队列的wwait进程队列上等待写的进程,将消息内容拷贝到用户空间的消息缓冲区msgp中,释放内核中该消息所占用的空间,返回

五 消息的控制

  1. 使用说明:
    int msgctl (int msqid, int cmd, struct msqid_ds *buf)
    ------msqid:为消息队列的qid
    ------cmd:为该函数要对消息队列执行的操作
    IPC_STAT:取出消息队列的msqid_ds结构体并将参数存入buf所指向的msqid_ds结构对象中
    IPC_SET:设定消息队列的msqid_ds 数据中的msg_perm 成员。设定的值由buf 指向的msqid_ds
    结构给出。
    IPC_EMID:将队列从系统内核中删除。
    ----buf:消息队列msqid_ds结构体指针
  2. 函数作用
    对消息队列进行设置以及相关操作,具体操作由cmd指定。
    问题:
  3. 在消息传递机制中,当读取一个消息后,消息将从队列移去,其他进程不能读到。若因为接收的缓冲区太小造成消息被截断,截断的部分将永远丢失。
  4. 进程必须通过带有IPC_RMID的sys _msgctl 调用,来显示的删除消息队列。如果不是这样则消息队列可以长久的存在。则样就回导致系统很难判断,消息是为了将来进程访问而留下来还是被无意的抛弃了,或是由于想要释放它的进程不正常终止了,这样导致系统无限的保存这消息。如果这种情况经常发生,这种消息资源就会用光。
  5. 总的说来,消息队列传递数据时是以一种不连续消息的方式,这样就可以更加灵活的处理数据。

四 实例

read.c

#include <unistd.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define IPC_KEY 1024
#define MAXLINE 80

void if_error(int stat_code, char* err_msg)
{
    if (stat_code < 0) {
        perror(err_msg);
        exit(errno);
    }
}

struct msg_ds
{
    char data[MAXLINE];
};

int main(int argc, char **argv)
{
    int msq_id, rd_n;
    struct msg_ds msg;

    msq_id = msgget(IPC_KEY, IPC_CREAT);
    if_error(msq_id, "msgget");

    while (1) {
        /* recv msg */
        rd_n = msgrcv(msq_id, (void*)&msg, MAXLINE, 0, 0);
        if_error(rd_n, "msgrcv");
        msg.data[rd_n] = '\0';
        /* exit msg */
        if (strncmp(msg.data, "exit", 4) == 0) break;

        printf("recv msg: %s\n", msg.data);
    }
    msgctl(msq_id, IPC_RMID, 0);
    return 0;
}

write.c

#include <unistd.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define IPC_KEY 1024
#define MAXLINE 80

void if_error(int stat_code, char* err_msg)
{
    if (stat_code < 0) {
        perror(err_msg);
        exit(errno);
    }
}

struct msg_ds
{
    char data[MAXLINE];
};

int main(int argc, char **argv)
{
    int msq_id, wr_n;
    struct msg_ds msg;

    msq_id = msgget(IPC_KEY, IPC_CREAT);
    if_error(msq_id, "msgget");

    while (1) {
        /* send msg */
        printf("send msg: ");
        fgets(msg.data, MAXLINE, stdin);
        msg.data[strlen(msg.data)-1] = '\0';

        wr_n = msgsnd(msq_id, &msg, MAXLINE, 0);
        if_error(wr_n, "msgsnd");
        /* exit msg */
        if (strncmp(msg.data, "exit", 4) == 0) break;
    }
    msgctl(msq_id, IPC_RMID, 0);
    return 0;
}
发布了67 篇原创文章 · 获赞 26 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/essity/article/details/82904276