进程间通信——消息队列(通俗易懂)

news/2024/9/28 17:24:23

消息队列

概念

  • 消息队列是消息的链表,存放在内核中并由消息队列标识符标识,消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺陷。消息队列包括 POSIX 消息队列和 System V 消息队列。

  • 消息队列是 UNIX 下不同进程之间实现共享资源的一种机制,UNIX 允许不同进程将格式化的数据流以消息队列形式发送给任意进程,有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。

原理

image-20240903085842708

步骤

  1. 创建或者获取消息队列
  2. 读取或写入数据
  3. 删除消息队列(非必须)

创建/获取消息队列

功能

  • 创建或者获取消息队列

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgget(key_t key, int msgflg);
  • keyIPC_PRIVATE 或 通过ftok()获取
  • msgflg
    • IPC_CREAT | 0666,内核中不存在指定队列就创建,否则就获取它
    • IPC_CREAT | IPC_EXCL | 0666,若队列已存在则出错(函数返回-1)

返回值

  • 成功返回 msgid(队列ID)
  • 失败返回-1,并设置errnor

使用命令获取消息队列

ipcs -q # interprocess communication status——进程间通信的状态 q——queue  

读取/写入数据

写入数据

功能

  • 往消息队列中写入数据

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  • msqidmsgget()的返回值(代表消息队列)

  • msgp:万能指针(写入的类型 + 数据)

    struct msgbuf {long mtype;			// 消息类型(>0)// 消息正文,多少字节随你而定// ...
    };
    
    • 只要保证首4字节是一个整数(代表类型)就行(64位系统,建议用 long 类型)

    • 正文部分是什么数据类型都没关系,因为消息队列传递的是 2 进制数据

    • 示例:

      struct msgbuf {long type;char name[20];int age;
      } msg;struct msgbuf {long type;int start;int end;
      } msg;
      
  • msgsz:数据域的字节数(不包含消息类型的大小)

    sizeof(struct msgbuf) - sizeof(long);
    
  • msgflg:0 阻塞 IPC_NOWAIT 不阻塞

消息队列的容量(msgp)

  • 消息的大小有一个最大限制,其定义在Linux/msg.h文件中

    #define MSGMAX 8192
    
  • 消息结构的总大小不能超过8192字节(包括type的大小)

返回值

  • 成功返回0
  • 失败返回-1,并设置 errno

读取数据

功能

  • 从消息队列中读取数据

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  • msqid:信息队列的ID号

  • msgp:用来保存读取的信息

    struct msgbuf {long mtype;			// 消息类型(>0)// 消息正文// ...
    };
    
  • msgsz:数据域的字节数

    sizeof(struct msgbuf) - sizeof(long);
    
  • msgtyp:读取的数据 在消息队列中存储的 类型

    • >0:得到该种类型的第一个数据
    • ==0:得到整个消息队列的第一个数据
    • <0:得到 小于或等于$|msgtyp|$类型的 最小类型的 第一个数据
  • msgflg:0 阻塞 IPC_NOWAIT 不阻塞

返回值

  • 成功返回 数据区的字节数
  • 失败返回-1,并设置 errno

删除消息队列

功能

  • 控制 消息队列

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • msqid:消息队列的ID号
  • cmd
    • IPC_RMID:删除消息队列,第三个参数为NULL

返回值

  • IPC_RMID 成功返回0,失败返回-1,并设置 errno

实现双向通信

方法一:创建两个消息队列来进行通信

image-20240903141847852

  • 不同颜色,代表不同的消息队列

two_msg_way1_1.c

  • 父进程中,使用通过 key1 建立的消息队列1,发送信息(数据类型为1)
  • 子进程中,使用通过 key2 建立的消息队列2,接受信息(数据类型为2)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <unistd.h>// 消息的缓冲区结构
struct msgmbuf
{long mtype;       // 消息类型char mtext[10];   // 消息内容
};int main(void)
{int            ret = -1;key_t          key1, key2;int            smsg_id, rmsg_id;        // 收消息与发消息的队列idstruct msgmbuf msg_mbuf;                // 创建消息缓冲区char*          msgpath1 = "/usr/bin";   // 消息key1产生所用的路径char*          msgpath2 = "/usr/bin";   // 消息key2产生所用的路径pid_t          pid;// 获得 key1 和 key2key1 = ftok(msgpath1, 'b');key2 = ftok(msgpath2, 'a');if (key1 != -1 && key2 != -1){printf("成功建立KEY\n");}else{perror("建立KEY失败");exit(-1);}smsg_id = msgget(key1, IPC_CREAT | 0666);   // 使用 key1 建立发消息的消息队列rmsg_id = msgget(key2, IPC_CREAT | 0666);   // 使用 key2 建立收消息的消息队列if (-1 == smsg_id || -1 == rmsg_id){perror("msgget error");exit(-1);}// 通过fork()创建子进程,主进程进行发消息,子进程进行收消息pid = fork();while (1){if (pid > 0)   // 主进程{msg_mbuf.mtype = 1;                           // 设置发送的消息类型scanf("%s", msg_mbuf.mtext);                  // 用户输入内容if (strncmp(msg_mbuf.mtext, "end", 3) == 0)   // 如果前三个字符为end,则跳出循环{break;}ret = msgsnd(smsg_id, &msg_mbuf, sizeof(struct msgmbuf) - sizeof(msg_mbuf.mtype),0);   // 发送消息if (ret == -1){perror("msgsnd error");exit(-1);}}else   // 子进程{ret = msgrcv(rmsg_id, &msg_mbuf, sizeof(struct msgmbuf) - sizeof(msg_mbuf.mtype), 2,0);   //接收消息if (-1 == ret){perror("msgrcv error");exit(-1);}else{printf("接收消息成功,长度:%d\n", ret);printf("content:%s\n\n", msg_mbuf.mtext);}}}// 注意:两个程序删除的消息队列ret = msgctl(smsg_id, IPC_RMID, NULL);   // 删除发消息的队列if (-1 == ret){perror("msgctl error");exit(-1);}return 0;
}

two_msg_way1_2.c

  • 父进程中,使用通过 key2 建立的消息队列2,发送信息(数据类型为2)
  • 子进程中,使用通过 key1 建立的消息队列1,接收信息(数据类型为1)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <unistd.h>// 消息的缓冲区结构
struct msgmbuf
{long mtype;       // 消息类型char mtext[10];   // 消息内容
};int main(void)
{int            ret = -1;key_t          key1, key2;int            smsg_id, rmsg_id;        // 收消息与发消息的队列idstruct msgmbuf msg_mbuf;                // 创建消息缓冲区char*          msgpath1 = "/usr/bin";   // 消息key1产生所用的路径char*          msgpath2 = "/usr/bin";   // 消息key2产生所用的路径pid_t          pid;// 获得 key1 和 key2key1 = ftok(msgpath1, 'b');key2 = ftok(msgpath2, 'a');if (key1 != -1 && key2 != -1){printf("成功建立KEY\n");}else{perror("建立KEY失败");exit(-1);}smsg_id = msgget(key2, IPC_CREAT | 0666);   // 使用 key2 建立收消息的消息队列rmsg_id = msgget(key1, IPC_CREAT | 0666);   // 使用 key1 建立发消息的消息队列if (-1 == smsg_id || -1 == rmsg_id){perror("msgget error");exit(-1);}// 通过fork()创建子进程,主进程进行发消息,子进程进行收消息pid = fork();while (1){if (pid > 0)   // 主进程{msg_mbuf.mtype = 2;                           // 设置发送的消息类型scanf("%s", msg_mbuf.mtext);                  // 用户输入内容if (strncmp(msg_mbuf.mtext, "end", 3) == 0)   // 如果前三个字符为end,则跳出循环{break;}ret = msgsnd(smsg_id, &msg_mbuf, sizeof(struct msgmbuf) - sizeof(msg_mbuf.mtype),0);   // 发送消息if (ret == -1){perror("msgsnd error");exit(-1);}}else   // 子进程{ret = msgrcv(rmsg_id, &msg_mbuf, sizeof(struct msgmbuf) - sizeof(msg_mbuf.mtype), 1,0);   //接收消息if (-1 == ret){perror("msgrcv error");exit(-1);}else{printf("接收消息成功,长度:%d\n", ret);printf("content:%s\n\n", msg_mbuf.mtext);}}}ret = msgctl(smsg_id, IPC_RMID, NULL);   // 删除发消息的队列if (-1 == ret){perror("msgctl error");exit(-1);}return 0;
}

方法二:通过创建不同的消息类型来进行双向通信

  • 在同一个消息队列中,使用不同的消息类型来标识收发信息

image-20240903190216427

  • 图中不同的颜色,表示不同的消息类型

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/56260.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

我克隆了我自己,数字生命有什么意义?

个人语音模型、个人视频模型、个人LLM语言模型都是基于开源的项目,但是需要进行少量的代码修改和集成的工作,这个过程不说很艰难,但是确实遇到很多问题。1. 场景... 2 2. 数字生命意义... 2 3. 具体实现思路... 4 1. 场景孩子:爸爸,今天天气不错,我们一起去打篮球吧?!…

Python批量分割Excel后逐行做差、合并文件的方法

本文介绍基于Python语言,针对一个文件夹下大量的Excel表格文件,基于其中每一个文件,首先依据某一列数据的特征截取我们需要的数据,随后对截取出来的数据逐行求差,并基于其他多个文件夹中同样大量的Excel表格文件,进行数据跨文件合并的具体方法~本文介绍基于Python语言,针…

手动脱壳学习笔记1----手动脱upx壳

ESP定律脱壳 例题 https://files.buuoj.cn/files/ee7f29503c7140ae31d8aafc1a7ba03f/attachment.tar 两下F9按一下F9,ESP变红在ESP处右键在内存窗口处转到在下面的内存下硬件断点再按一下F9在401280处下断点scylla插件输入00401820最后处理

我的第一次随笔

软件工程 https://edu.cnblogs.com/campus/fzu/SE2024/join?id=CfDJ8AOXHS93SCdEnLt5HW8VuxRT6AQqVs0mNHhOMFCtt8IMscU6Av_iCXeJ1yrZksLek4Sb4eQ33ielNgQZ17WNvtTHzyIIaHFdy4c1XbJ0CE2yknMRYdVBQ0jOrgoKTOKwvcAZuUiQqpoOYBFbKCcvozo要求 https://edu.cnblogs.com/campus/fzu/S…

应用程序报错漏洞修复

漏洞描述 错误页面由服务器产生400、403、404、500等错误时,返回详细错误信息。报错信息中可能会包含服务器代码信息、服务器版本信息、模板类型、数据库连接信息、SQL语句或者敏感文件的路径。修复 修改nginx配置# 其他配置...server {# 其他配置...# 指定 400 错误页面error…

PbootCMS模板调用幻灯片轮播图及参数说明

在 PbootCMS 中,{pboot:slide} 标签用于输出指定分组的幻灯片。以下是一个详细的示例,展示了如何使用 {pboot:slide} 标签来输出指定分组的幻灯片,并控制显示的数量。扫码添加技术【解决问题】专注中小企业网站建设、网站安全12年。熟悉各种CMS,精通PHP+MYSQL、HTML5、CSS3…

2-SAT 学习笔记

一、简介 k-SAT (satisfiability) 解决这样一类问题:给定 \(n\) 个布尔变量和 \(m\) 条限制,每条限制形如 \(x_1=0/1\or\cdots\or x_n=0/1\) ,求是否有解并给出构造。 当 \(k\gt 2\) 时,该问题为 NP 完全问题。 二、算法流程 在学习本算法前,请确保你对有向图强连通分量有…

pbootcms模板首页如何调用指定栏目的子栏目

在 PbootCMS 中,可以通过 {pboot:nav} 标签来调用指定栏目的子栏目。以下是具体的实现方法和示例代码。 示例代码html{pboot:nav parent=4 num=7} <a href="[nav:link]">[nav:name]</a> {/pboot:nav}参数说明parent: 指定父栏目的 ID。示例中的 parent=…