当前位置: 首页 > news >正文

进程间通信之消息队列

目录

四个关键函数

基本原理

int msgget(key_t key, int msgflg)

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

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

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

示例代码

测试msgrcv

内核数据结构msqid_ds


四个关键函数

  • int msgget(key_t key, int msgflg);用于创建一个新的消息队列或者获取一个已有的消息队列标识符。成功时返回消息队列的标识符(类似于信箱的编号),失败时返回 -1
  • int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);用于向指定的消息队列发送一条消息。成功时返回 0,失败时返回 -1
  • ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);从指定的消息队列中接收一条消息。成功时返回接收到消息的大小(不包括 mtype 成员的大小),失败时返回 -1
  • int msgctl(int msqid, int cmd, struct msqid_ds *buf);用于对消息队列执行各种控制操作,比如删除消息队列、获取或设置消息队列的属性等。成功时返回 0,失败时返回 -1

基本原理

  • msgsnd每次发送数据时,都必须给数据标记一个类型,例如类型为2的数据
  • 然后这个有类型的数据进入消息队列的最后一个位置
  • msgrcv在接收数据时也要说明自己想要接收那种数据,如上图此时msgrcv想要接收类型为2的消息,于是msgrcv会从消息队列中取出第一个类型为2的消息
  • 细节问题后续会讨论

int msgget(key_t key, int msgflg)

  • key:是一个用来标识消息队列的键值。它可以是一个任意的整数值,但通常使用 ftok 函数生成。就好比是给 “信箱” 一个独特的编号,方便找到对应的 “信箱”。例如,通过 ftok 函数可以基于文件路径和一个项目 ID 生成一个唯一的键值,这样不同进程只要使用相同的文件路径和项目 ID 就能找到同一个消息队列。
  • msgflg:用于指定消息队列的创建标志和访问权限。例如,IPC_CREAT 表示如果消息队列不存在就创建它,0666 表示设置消息队列的读写权限为所有用户可读可写。

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

  • msqid:是由 msgget 函数返回的消息队列标识符,指定要往哪个 “信箱” 投递消息。
  • msgp:是一个指向要发送消息结构的指针。这个消息结构必须以一个 long 类型的成员开头,用来表示消息的类型
  • msgsz:指定要发送消息的大小(不包括 mtype 成员的大小),也就是信的内容长度。
  • msgflg:用于指定发送消息的方式。比如 IPC_NOWAIT 表示如果消息队列已满,不等待直接返回。默认情况下,发送消息时如果消息队列满了,则msgsnd将阻塞。若IPC_NOWAIT标志被指定,则msgsnd将立即返回并设置errno为EAGAIN。
  • 发送消息的结构如下:
struct mymsgbuf {long mtype;char mtext[100];
};

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

  • msqid:消息队列标识符,指定从哪个 “信箱” 取信。
  • msgp:指向一个用来接收消息的结构的指针,结构类型要和发送时一致。
  • msgsz:指定接收消息的最大长度(不包括 mtype 成员的大小)。
  • msgtyp:指定要接收的消息类型。如果为 0,则接收消息队列中的第一条消息;如果大于 0,则接收类型等于 msgtyp 的第一条消息;如果小于 0,则接收类型小于等于 msgtyp 绝对值的第一条消息。
  • msgflg:用于指定接收消息的方式,如 IPC_NOWAIT 表示如果没有符合条件的消息,不等待直接返回,并设置errno为ENOMSG。MSG_EXCEPT:如果msgtype大于0,则接收消息队列中第一个非msgtype类型的消息。MSG_NOERROR:如果消息数据部分的长度超过了msg_sz,就将它截断。

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

  • msqid:消息队列标识符。
  • cmd:指定要执行的控制命令。常见的有 IPC_STAT(获取消息队列的状态信息并存储在 buf 中)、IPC_SET(设置消息队列的属性,属性值取自 buf)、IPC_RMID(删除消息队列)。
  • buf:指向一个 msqid_ds 结构的指针,用于获取或设置消息队列的属性。当 cmd 为 IPC_STAT 或 IPC_SET 时使用,IPC_RMID 时可设为 NULL

示例代码

        该代码仅用于展示消息队列的基本逻辑,针对没有测试的其他情况,大家需要使用时自行测试即可。
 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <error.h>
#include <errno.h>#define MAX_TEXT 512// 定义消息结构
struct my_msg_st {long my_msg_type;char some_text[MAX_TEXT];
};void handle_errors(const char *msg) {perror(msg);exit(EXIT_FAILURE);
}int main() {int msgid;key_t key;struct my_msg_st some_data;long msg_to_receive = 0;// 创建一个唯一的键值key = ftok(".", 'a');if (key == -1) {handle_errors("ftok");}// 创建消息队列msgid = msgget(key, 0666 | IPC_CREAT);if (msgid == -1) {handle_errors("msgget");}// 发送不同类型消息的示例// 发送类型为1的消息some_data.my_msg_type = 1;strcpy(some_data.some_text, "This is a type 1 message");//if (msgsnd(msgid, (void *)&some_data, strlen(some_data.some_text), 0) == -1) {handle_errors("msgsnd");}// 发送类型为2的消息some_data.my_msg_type = 2;strcpy(some_data.some_text, "This is a type 2 message");if (msgsnd(msgid, (void *)&some_data, strlen(some_data.some_text), 0) == -1) {handle_errors("msgsnd");}// 接收消息的各种情况示例// 接收类型为1的消息msg_to_receive = 1;if (msgrcv(msgid, (void *)&some_data, MAX_TEXT, msg_to_receive, 0) == -1) {handle_errors("msgrcv");}printf("Received type 1 message: %s\n", some_data.some_text);// 接收类型为0的消息(即队列中的第一条消息)msg_to_receive = 0;if (msgrcv(msgid, (void *)&some_data, MAX_TEXT, msg_to_receive, 0) == -1) {handle_errors("msgrcv");}printf("Received first message in the queue: %s\n", some_data.some_text);// 使用IPC_NOWAIT标志接收消息,如果队列为空则不等待msg_to_receive = 1;if (msgrcv(msgid, (void *)&some_data, MAX_TEXT, msg_to_receive, IPC_NOWAIT) != -1) {printf("Received message with IPC_NOWAIT: %s\n", some_data.some_text);} else if (errno == ENOMSG) {printf("No message of type %ld in the queue with IPC_NOWAIT.\n", msg_to_receive);} else {handle_errors("msgrcv with IPC_NOWAIT");}// 删除消息队列if (msgctl(msgid, IPC_RMID, 0) == -1) {handle_errors("msgctl");}return 0;
}

测试msgrcv

  1. msgrcv默认是阻塞的,如果消息队列中没有自己想要的数据会被阻塞
    // 发送类型为1的消息some_data.my_msg_type = 1;strcpy(some_data.some_text, "This is a type 1 message");//if (msgsnd(msgid, (void *)&some_data, strlen(some_data.some_text), 0) == -1) {handle_errors("msgsnd");}// 发送类型为2的消息some_data.my_msg_type = 2;strcpy(some_data.some_text, "This is a type 2 message");if (msgsnd(msgid, (void *)&some_data, strlen(some_data.some_text), 0) == -1) {handle_errors("msgsnd");}// 接收类型为3的消息msg_to_receive = 3;if (msgrcv(msgid, (void *)&some_data, MAX_TEXT, msg_to_receive, 0) == -1) {handle_errors("msgrcv");}printf("Received type 1 message: %s\n", some_data.some_text);

  2. msgrcv设置了IPC_NOWAIT 后,如果消息队列中没有自己想要的数据,不等待直接返回,并设置errno为ENOMSG。

     // 发送类型为1的消息some_data.my_msg_type = 1;strcpy(some_data.some_text, "This is a type 1 message");//if (msgsnd(msgid, (void *)&some_data, strlen(some_data.some_text), 0) == -1) {handle_errors("msgsnd");}// 发送类型为2的消息some_data.my_msg_type = 2;strcpy(some_data.some_text, "This is a type 2 message");if (msgsnd(msgid, (void *)&some_data, strlen(some_data.some_text), 0) == -1) {handle_errors("msgsnd");}// 使用IPC_NOWAIT标志接收消息,如果队列为空则不等待msg_to_receive = 3;if (msgrcv(msgid, (void *)&some_data, MAX_TEXT, msg_to_receive, IPC_NOWAIT) != -1) {printf("Received message with IPC_NOWAIT: %s\n", some_data.some_text);} else if (errno == ENOMSG) {printf("No message of type %ld in the queue with IPC_NOWAIT.\n", msg_to_receive);} else {handle_errors("msgrcv with IPC_NOWAIT");}

  3. 其他情况例如MSG_EXCEPT标志等情况,大家自行测试即可,重在了解消息队列的逻辑。

内核数据结构msqid_ds

        如果msgget用于创建消息队列,则与之关联的内核数据结构msqid_ds将被创建开初始化。msqid ds结构体的定义如下:

struct msqid_ds {struct ipc_perm msg_perm;    /* 消息队列的权限结构 */time_t          msg_stime;   /* 最后一次发送消息的时间 */time_t          msg_rtime;   /* 最后一次接收消息的时间 */time_t          msg_ctime;   /* 最后一次修改消息队列状态的时间 */unsigned long   __msg_cbytes; /* 消息队列中当前数据的字节数 */msgqnum_t       msg_qnum;    /* 消息队列中的消息数量 */msglen_t        msg_qbytes;  /* 消息队列允许的最大字节数 */pid_t           msg_lspid;   /* 最后一次发送消息的进程ID */pid_t           msg_lrpid;   /* 最后一次接收消息的进程ID */
};

        msgsnd成功时将修改内核数据结构msqid_ds的部分字段,如下所示:

  • 将msg_qnum加1。
  • 将msg_lspid设置为调用进程的PID。
  • 将msg_stime设置为当前的时间。

        msgrcv成功时将修改内核数据结构msqid_ds的部分字段,如下所示:

  • 将msg_qnum减1。
  • 将msg_Irpid设置为调用进程的PID。
  • 将msg_rtime设置为当前的时间。

        msqid_ds数据结构就是内核实现消息队列的底层逻辑,大家简单了解即可。

http://www.xdnf.cn/news/1008145.html

相关文章:

  • 大厂机试题解法笔记大纲+按知识点分类+算法编码训练
  • Coze搭建工作流
  • DWS层新增指标处理方案
  • 工程项目管理软件选型指南:核心功能、技术架构与行业实践
  • 获取分布式锁
  • 医院部署IBMS系统时,哪些关键因素需要重点权衡与规划
  • 【C语言】*与深层理解
  • 【Vue3/Typescript】从零开始搭建H5移动端项目
  • 【二分模版------左闭右闭】
  • Vue ⑨-Pinia
  • c++ - 关于 string 的练习题
  • 《深度剖析:Java中用Stanford NLP工具包优化命名实体识别》
  • Redis哨兵机制
  • 获取Unity节点路径
  • ✅ [Dify]明道云同步内容到 Dify 知识库的最佳实践指南
  • 电梯钢带安全无盲区:电梯钢带断丝智慧监测方案让隐患“毫秒现形“
  • SpringCloud-seata集成到nacos
  • 实战二:基于网页端实现与大模型的问答交互
  • 虚拟 DOM Diff 算法详解
  • UE5场景漫游——鼠标控制旋转与第一人称漫游
  • 51la批量创建站点繁琐?悟空统计一站式高效解决方案
  • Spring Data REST技术详解与应用实践
  • HALCON第四讲->几何变换
  • SX1268低功耗sub-1g芯片支持lora和GFSK调制
  • MATLAB griddatan 函数支持的插值方法MATLAB 的 griddatan 函数主要支持以下几种插值方法
  • 关于等效偶极子的概念理解
  • QT5 隐藏控制台窗口方法2025.6.12
  • 【Java面试笔记:实战】41、Java面试核心考点!AQS原理及应用生态全解析
  • FastDFS 分布式文件系统
  • 设计一个类似支付宝或微信支付的在线支付系统