Linux系统编程系列之进程间通信-消息队列

 Linux系统编程系列(16篇管饱,吃货都投降了!)

        1、Linux系统编程系列之进程基础

        2、Linux系统编程系列之进程间通信(IPC)-信号

        3、Linux系统编程系列之进程间通信(IPC)-管道

        4、Linux系统编程系列之进程间通信-IPC对象

        5、Linux系统编程系列之进程间通信-消息队列

        6、Linux系统编程系列之进程间通信-共享内存

        7、Linux系统编程系列之进程间通信-信号量组

        8、Linux系统编程系列之守护进程

        9、Linux系统编程系列之线程

        10、Linux系统编程系列之线程属性 

        11、Linux系统编程系列之互斥锁和读写锁

        12、Linux系统编程系列之线程的信号处理

        13、Linux系统编程系列之POSIX信号量

        14、Linux系统编程系列之条件变量

        15、Linux系统编程系列之死锁

        16、 Linux系统编程系列之线程池

一、什么是消息队列

        消息队列是system-V三种IPC对象之一,是进程间通信的一种方式。

二、消息队列的特性

        允许发送的数据携带类型(指定发送给谁),具有相同类型的数据在消息队列内部排队,读取的时候也要指定类型,然后依次读出数据。可以理解为消息队列就是一个多管道集合

三、消息使用场景

        由于每个消息都携带有类型,相同的类型自成一队,因此读取方向可以根据类型来挑选不同的队列,所以说MSG适用于“多对一”的场景。

        (1)系统日志。

        (2)多个不同的,不相关的进程向同一管道输入数据。

四、函数API接口

        1、指定消息队列功能

// 指定消息队列功能
int msgget(key_t key, int msgflg);

// 接口说明:
    返回值:消息队列MSG对象ID
    参数key:键值,全局唯一标识,可由ftok()产生
    参数msgflg:操作模式与读写权限,与文件操作函数open类似。

         2、向MSG对象发送消息

// 向MSG对象发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

// 接口说明
    msggid:MSG对象的ID,由msgget()获取
    msgp:一个指向等待被发送的消息的指针,由于MSG中的消息最大的特点是必须有一个整数标识,用以区分MSG中的不同的消息,因此MSG的消息会使用一个特别的结构体来表达:

struct msgbuf
{
    long mtype;    // 消息类型固定,必须大于0
    char text[100];    // 消息正文(可变,自己定义)
    
};

    msgsz:消息正文的长度(单位字节),注意不含类型长度
    msgflg:发送选项
    0:默认发送模式,在MSG缓冲区已满的情形下阻塞,直到缓冲区变为可用状态
    IPC_NOWAIT:非阻塞发送模式,在MSF缓冲区已满的情形下直接退出函数并设置错误码为EAGAIN

        3、从MSG对象接收消息

// 从MSG对象接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

// 接口说明
    msgqid:MSG对象ID,由msgget()获取
    msgp:存放消息的内存入口
    msgsz:存放消息的内存大小
    msgtyp:欲接收消息的类型
        0:不区分类型,自己读取MSG中的第一个消息
        大于0:读取类型为指定msgtyp的第一个消息(若msgfla被配置了MSG_EXCEPT则读取除了类型msgtyp的第一个消息)
        小于0:读取类型小于等于msgtyp绝对值的第一个具有最小类型的消息。
    msgflg:接收选项
        0:默认接收模式,在MSG中无指定类型消息时阻塞
        IPC_NOWAIT:非阻塞接收模式,在MSG中无指定类型消息时直接退出函数并设置错误码为ENOMSG
        MSG_EXCEPT:读取出msgtyp之外的第一个消息
        MSG_NOERROR:如果待读取的消息尺寸比msgsz大,直接切割消息并返回msgsz部分,读不了的部分直接丢弃。若没有设置该项,则函数将出错返回并设置错误码为E2BIG

        4、删除消息队列对象

        在前一篇博客IPC对象中有讲,可以使用命令来删除,同时也可以采用函数的方式来删除

// 删除MSG对象
int msgctl(int msqid, int cmd, struct msgqid_ds *buf);

// 接口说明
msqid:MSG对象ID
cmd:控制命令字
    IPC_STAT:获取该MSG的信息,储存在结构体msgqid_ds中
    IPC_SET: 设置该MSG的信息,储存在结构体msgqid_ds中
    IPC_RMID:立即删除该MSG,并且唤醒所有阻塞在该MSG上的进程,同时忽略第三个参数

五、消息队列使用步骤

        1、使用ftok()获取key值

        2、调用msgget(),指定为消息队列

        3、调用msgsnd()或者msgrcv()来发送或者接收信息。

        4、使用命令或者函数删除消息队列(可选)。

六、案例

        使用消息队列来实现两个进程间进行相互收发信息

// 消息队列的案例

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

// 编译版本,一个直接编译,另外一个把0x01和0x02位置互换后编译
#define SEND_MTYPE 0x01
#define RECV_MTYPE 0x02

struct msgbuf 
{
    long mtype;      // 消息类型,固定的,必须大于0 
    char mtext[32];  // 可变的,自己定义
};


int main(int argc, char *argv[])
{
    // 1、获得KEY值
    key_t msg_key = ftok("./", 1);
    if(msg_key == -1)
    {
        perror("ftok fail");
        return -1;
    }

    // 2、指定消息队列功能
    int msg_id = msgget(msg_key, IPC_CREAT | 0666);
    if(msg_id == -1)
    {
        perror("msgget fail");
        return -1;
    }

    // 3、消息队列的读写操作
    struct msgbuf Buf = {0};
    int ret = 0;
    pid_t pid = fork();
    // 父进程
    if(pid > 0)
    {   
        Buf.mtype = SEND_MTYPE; // 指定消息类型
        while(1)
        {
            printf("please input data:\n");
            // fgets("%s", Buf.mtext);
            fgets(Buf.mtext, sizeof(Buf.mtext), stdin);
            // 发送消息
            ret = msgsnd(msg_id, &Buf, sizeof(Buf.mtext), 0);
            if(ret == -1)
            {
                perror("msgsnd fail");
            }
            else if(ret == 0)
            {
                printf("send data success: %s\n", Buf.mtext);
            }
            memset(Buf.mtext, 0, sizeof(Buf.mtext));
        }
    }
    // 子进程
    else if(pid == 0)
    {
        Buf.mtype = RECV_MTYPE; // 指定消息类型
        while(1)
        {
            // 接收消息
            ret = msgrcv(msg_id, &Buf, sizeof(Buf.mtext), Buf.mtype, 0);
            if(ret > 0)
            {
                printf("read data success: %s\n", Buf.mtext);
            }
        }
    }
    else
    {
        perror("fork fail");
        return -1;
    }

    return 0;
}

        注:编译时,编译两个版本,一个直接编译,另外一个把两个宏定义的数值互换位置再编译

七、总结

        消息队列可以理解为一个多管道的集合,通常用于多对一的场景,操作消息队列需要遵循一定的步骤,可以结合案例来加深对消息队列的理解。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值