Linux多进程进阶---进程间通信《消息队列》(四)


一、消息队列的原理

  实现原理:具有相同IPC命名空间(structipc_namespace)的进程能够同时访问IPC命名空间相同内存空间。
在这里插入图片描述
在这里插入图片描述
Key,标识ID,IPC对象
Key:整型数,用来索引(哈希表)IPC对象,检索失败创建IPC对象并返回标识ID,检索成功返回标识ID。
标识ID:整型数,用来索引(radix树)IPC对象,标识ID并不由Key产生,而是由其他信息(如序号)通过算法产生。
IPC对象:消息队列继承IPC对象,提供一些通用信息。

二、消息队列的特点

1.不受进程的生命周期影响。
2.可以实现多个进程同时进行写入与读出,可以实现并发处理。
3.可以通过消息优先级机制,实现对重要的消息优先处理。# 二、使用步骤

三、所用函数

//1.msgget函数
//创建一个新的消息队列(如果指定IPC_CREAT标志且队列尚不存在)或打开一个已存在的消息队列。

int msgget(key_t key, int msgflg);

//key:由ftok生成的键值,用于标识消息队列。
//msgflg:权限位和标志位的组合,如IPC_CREAT(如果队列不存在则创建)和访问权限(如0666)。

//2msgsnd函数
//功能:发送一个消息到消息队列。

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//msqid:由msgget返回的消息队列标识符。
//msgp:指向要发送的消息的指针,通常是一个结构体指针,如struct mymsgbuf *。
//msgsz:消息正文的字节数,不包括消息类型的字节。
//msgflg:可选标志,如IPC_NOWAIT(非阻塞发送)。
//结构体示例(常用的消息缓冲区结构体):
typedef struct {
    long mtype;       // 消息类型,长整型,用户自定义,当作优先级来使用
    char mtext[100];  // 消息正文,最大长度由用户定义
} mymsgbuf;

//3.msgrcv函数
//功能:从消息队列接收一个消息。

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

//msqid:消息队列标识符。
//msgp:指向接收消息缓冲区的指针,通常是一个结构体指针。
//msgsz:缓冲区能接收的最大消息正文的字节数。
//msgtyp:要接收的消息类型,若为0,则接收任何类型的消息。
//msgflg:可选标志,如IPC_NOWAIT(非阻塞接收),MSG_NOERROR(即使消息过长也不截断)。

//4.msgctl函数
//功能:控制消息队列的各种属性,包括获取状态、修改权限和删除消息队列。

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

//msqid:消息队列的标识符。
//cmd:操作命令,可以是
        IPC_STAT(获取队列状态到buf)
        IPC_SET(设置队列属性)
        IPC_RMID(删除消息队列)
//buf:指向struct msqid_ds结构体的指针,用于存储或修改消息队列的状态信息。



//结构体msqid_ds示例:
struct msqid_ds {
    struct ipc_perm msg_perm;   // 权限信息
    __kernel_time_t msg_stime;  // 最后一次消息发送时间
    __kernel_time_t msg_rtime;  // 最后一次消息接收时间
    __kernel_time_t msg_ctime;  // 最后一次改变队列时间(权限或状态)
    unsigned long msg_cbytes;   // 当前队列中消息的总字节数
    unsigned long msg_qnum;     // 队列中消息的数量
    unsigned long msg_qbytes;   // 队列的当前最大容量(字节)
    pid_t msg_lspid;            // 最后发送消息的进程ID
    pid_t msg_lrpid;            // 最后接收消息的进程ID
};

// ipc_perm结构体定义,嵌套在msqid_ds中
struct ipc_perm {
    key_t __key;                // 键值,用于标识IPC对象
    uid_t uid;                  // 所有者的用户ID
    gid_t gid;                  // 所有者的组ID
    uid_t cuid;                 // 创建者的用户ID
    gid_t cgid;                 // 创建者的组ID
    unsigned short mode;        // 权限位,如读、写等
    unsigned short __seq;       // 序列号,内核使用
    unsigned long __pad1;       // 填充字段
    unsigned long __pad2;       // 填充字段
};

其中msgget函数的返回值需要注意,如下表:
在这里插入图片描述

四、示例代码

发送端

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

#define MSGSIZE 100
typedef struct mymsgbuf {
    long mtype;       // 消息类型
    char mtext[MSGSIZE]; // 消息正文
} mymsgbuf;

int main() {
    int msqid;      // 消息队列标识符
    key_t key;     // 键值
    mymsgbuf message; // 消息缓冲区

    // 使用ftok生成一个键值,基于项目目录下的一个文件路径和一个项目标识符
    key = ftok("./sender.c", 'A');
    if (key == (key_t)-1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }

    // 尝试创建或打开一个消息队列,如果不存在则创建(IPC_CREAT)
    if ((msqid = msgget(key, IPC_CREAT | 0666)) == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }

    // 准备消息内容
    message.mtype = 1; // 消息类型
    strcpy(message.mtext, "Hello from Sender!");

    // 发送消息到消息队列
    if (msgsnd(msqid, &message, sizeof(message), 0) == -1) {
        perror("msgsnd");
        exit(EXIT_FAILURE);
    }

    printf("Message sent.\n");

    // 可以在这里添加代码使用msgctl来操作消息队列,比如获取状态或删除消息队列

    return 0;
}

接收端

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

#define MSGSIZE 100
typedef struct mymsgbuf {
    long mtype;       // 消息类型
    char mtext[MSGSIZE]; // 消息正文
} mymsgbuf;

int main() {
    int msqid;      // 消息队列标识符
    key_t key;     // 键值
    mymsgbuf message; // 消息缓冲区

    // 使用相同的ftok调用来获取与发送进程相同的键值
    key = ftok("./sender.c", 'A');
    if (key == (key_t)-1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }

    // 打开已存在的消息队列
    if ((msqid = msgget(key, 0)) == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }

    // 接收消息,这里我们假设只接收类型为1的消息
    if (msgrcv(msqid, &message, MSGSIZE, 1, 0) == -1) {
        perror("msgrcv");
        exit(EXIT_FAILURE);
    }

    printf("Received message: %s\n", message.mtext);

    // 如果不再需要消息队列,可以在这里使用msgctl删除它
    // struct msqid_ds buf;
    // msgctl(msqid, IPC_RMID, &buf);

    return 0;
}

单独展示msgctl的用法

//1.IPC_STAT
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int main() {
    int msqid; // 假设这是您已有的消息队列标识符
    struct msqid_ds buf;

    // 获取消息队列状态
    if (msgctl(msqid, IPC_STAT, &buf) == -1) {
        perror("msgctl IPC_STAT");
        return 1;
    }

    printf("Message Queue Status:\n");
    printf("  msg_perm.uid: %d\n", buf.msg_perm.uid);
    printf("  msg_perm.gid: %d\n", buf.msg_perm.gid);
    printf("  msg_perm.mode: %o\n", buf.msg_perm.mode);
    printf("  msg_qnum: %ld\n", buf.msg_qnum); // 当前队列中的消息数量
    printf("  msg_qbytes: %ld\n", buf.msg_qbytes); // 队列的最大容量
    // 其他字段打印...

    return 0;
}

//2.IPC_SET
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>

int main() {
    int msqid; // 假设这是您已有的消息队列标识符
    struct msqid_ds buf;

    // 首先获取当前状态
    if (msgctl(msqid, IPC_STAT, &buf) == -1) {
        perror("msgctl IPC_STAT");
        return 1;
    }

    // 修改权限,例如,只允许所有者读写
    buf.msg_perm.mode = S_IRUSR | S_IWUSR;

    // 设置新的权限
    if (msgctl(msqid, IPC_SET, &buf) == -1) {
        perror("msgctl IPC_SET");
        return 1;
    }

    printf("Message queue permissions set to owner read-write.\n");
    return 0;
}
//3.IPC_RMID
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int main() {
    int msqid; // 假设这是您已有的消息队列标识符

    // 删除消息队列
    if (msgctl(msqid, IPC_RMID, NULL) == -1) {
        perror("msgctl IPC_RMID");
        return 1;
    }

    printf("Message queue successfully deleted.\n");
    return 0;
}
  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值