进程间通信方式2-------消息队列

el/2024/3/2 10:33:34

目录

消息队列
最简单的消息队列的使用流程
消息队列(参考资料)
https://www.cnblogs.com/wuyepeng/p/9748728.html
https://blog.csdn.net/ljianhui/article/details/10287879


消息队列

什么是消息队列?

  1. 消息队列,Unix的通信机制之一,可以理解为是一个存放消息(数据)容器。将消息写入消息队列,然后再从消息队列中取消息,一般来说是先进先出的顺序。可以解决两个进程的读写速度不同(处理数据速度不同),系统耦合等问题,而且消息队列里的消息哪怕进程崩溃了也不会消失。
  2. 消息队列实际上是一个消息的链表,存放在内核中,由系统维护。
  3. Linux用宏MSGMAXMSGMNB来限制一条消息的最大长度和一个队列的最大长度。

最简单的消息队列的使用流程

①ftok函数生成键值

②msgget函数创建消息队列

③msgsnd函数往消息队列发送消息

④msgrcv函数从消息队列读取消息

⑤msgctl函数进行删除消息队列

一个消息数据应该由以下一个结构体组成,举个例子:

struct mymesg{
long int mtype;	//类,消息队列可以控制读取相应类型的数据,这时就不一定是先进先出的顺序了,文章后面会继续介绍
char mtext[size_t];	//数据,传递的数据存放在这里面
};

1.ftok函数生成键值

每一个消息队列都有一个对应的键值(key)相关联(共享内存、信号量也同样需要)。
#include<sys/ipc.h>

key_t ftok(const char *path ,int id);

path为一个已存在的路径名

id为0~255之间的一个数值,代表项目ID,自己取

返回值:成功返回键值(相当于32位的int)。出错返回-1

例如:key_t key = ftok( “/tmp”, 66);

关于ftok()函数的一个陷阱

在使用ftok()函数时,里面有两个参数,即fname和id,fname为指定的文件名,而id为子序列号,这个函数的返回值就是key,它与指定的文件的索引节点号和子序列号id有关,这样就会给我们一个误解,即只要文件的路径,名称和子序列号不变,那么得到的key值永远就不会变。事实上,这种认识是错误的,想想一下,假如存在这样一种情况:在访问同一共享内存的多个进程先后调用ftok()时间段中,如果fname指向的文件或者目录被删除而且又重新创建,那么文件系统会赋予这个同名文件新的i节点信息,于是这些进程调用的ftok()都能正常返回,但键值key却不一定相同了。由此可能造成的后果是,原本这些进程意图访问一个相同的共享内存对象,然而由于它们各自得到的键值不同,实际上进程指向的共享内存不再一致;如果这些共享内存都得到创建,则在整个应用运行的过程中表面上不会报出任何错误,然而通过一个共享内存对象进行数据传输的目 的将无法实现。

这是一个很重要的问题,希望能谨记!!!

所以要确保key值不变,要么确保ftok()的文件不被删除,要么不用ftok(),指定一个固定的key值。

2.msgget()函数

#include<sys/msg.h>函数原型:int msgget(key_t key,int flag);

key为ftok生成的键值

flag为所需要的操作和权限,可以用来控制创建一个消息队列。

  • flag的值为IPC_CREAT:如果不存在key值的消息队列,且权限不为0,则创建消息队列,并返回一个消息队列ID。如果存在,则直接返回消息队列ID。

  • flag的值为 IPC_CREAT | IPC_EXCL:如果不存在key值的消息队列,且权限不为0,则创建消息队列,并返回一个消息队列ID。如果存在,则产生错误。

返回值:成功返回消息队列ID;出错返回-1

int id = msgget(key,IPC_CREAT|IPC_EXCL|0666);创建一个权限为0666(所有用户可读可写,具体查询linux权限相关内容)的消息队列,并返回一个整形消息队列ID,如果key值已经存在有消息队列了,则出错返回-1。

int id = msgget(key,IPC_CREAT|0666);创建一个权限为0666(所有用户可读可写,具体查询linux权限相关内容)的消息队列,并返回一个消息队列ID,如果key值已经存在有消息队列了,则直接返回一个消息队列ID。

3.msgsnd函数

#include<sys/msg.h>

该函数用来把消息添加到消息队列中。它的原型为:

int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);

msgid是由msgget函数返回的消息队列标识符。

msg_ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样:

struct my_message {long int message_type;/* The data you wish to transfer */
};

msg_sz 是msg_ptr指向的消息的长度注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员变量的长度。

msgflg 用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。

如果调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1.

4.msgrcv函数

#include<sys/msg.h>

该函数用来从一个消息队列获取消息,它的原型为:

int  msgrcv(int msgid,void *ptr,size_t nbytes,long type,int flag);

msgid:为msgget返回的消息队列ID值

ptr:为消息结构体mymesg指针

nbytes:为消息结构体mymesg里的字符数组mtext大小,sizeof(mtext)

type:在结构体mymesg里我们定义了一个long int mtype,用于分辨消息的类型

type ==0 返回队列中的第一个消息type > 0 返回队列中消息类型为type的第一个消息type < 0 返回队列中消息类型值小于等于type绝对值的消息,如果这种消息有若干个,则取类型值最小的消息

flag:可以为0、IPC_NOWAITIPC_EXCEPT

为0时,阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待为IPC_NOWAIT时,如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG为IPC_EXCEPT时,与msgtype配合使用返回队列中第一个类型不为msgtype的消息

返回值:成功返回消息数据部分的长度;错误返回-1

例如:msgrcv(id,(void *)&ckxmsg,512,1,0);

注:其中512为字符数组长度,1为消息类型为1,则接受函数接受消息类型为1的消息

5.msgctl函数

#include<sys/msg.h>

该函数用来控制消息队列,它与共享内存的shmctl函数相似,它的原型为:

int msgctl(int msgid, int command, struct msgid_ds *buf);

command是将要采取的动作,它可以取3个值,

  • `IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
  • IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
  • IPC_RMID:删除消息队列

buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员:

struct msgid_ds
{uid_t shm_perm.uid;uid_t shm_perm.gid;mode_t shm_perm.mode;
};

返回值:成功返回0;错误返回-1

例如:msgctl(id,IPC_RMID,NULL);删除id号的消息队列

6.fget函数

C 库函数 char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定

char *fgets(char *str, int n, FILE *stream)

参数:

  • str – 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
  • n – 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。

如果成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。

如果发生错误,返回一个空指针。

7.strncmp函数

C 库函数 int strncmp(const char *str1, const char *str2, size_t n)str1str2 进行比较,最多比较前 n 个字节。

int strncmp(const char *str1, const char *str2, size_t n)

参数:

  • str1 – 要进行比较的第一个字符串。
  • str2 – 要进行比较的第二个字符串。
  • n – 要比较的最大字符数。

如果返回值 < 0,则表示 str1 小于 str2。

如果返回值 > 0,则表示 str2 小于 str1。

如果返回值 = 0,则表示 str1 等于 str2。


编写两个程序,msgreceive()和msgsend()来表示接收和发送信息。根据正常的情况,我们允许两个程序都可以创建消息,但只有接收者在接收完最后一个消息之后,它才把它删除。

msgsend():

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/msg.h>
#include<sys/ipc.h>
struct mymesg{long int mtype;char mtext[512];
};
int main()
{int id = 0;struct mymesg ckxmsg;key_t key = ftok("/tmp",66);id = msgget(key,IPC_CREAT | 0666);if(id == -1){printf("create msg error \n");return 0;}while(1){char msg[512];memset(msg,0,sizeof(msg));ckxmsg.mtype = 1;printf("input message:");fgets(msg,sizeof(msg),stdin);strcpy(ckxmsg.mtext,msg);if(msgsnd(id,(void *)&ckxmsg,512,0) < 0){printf("send msg error \n");return 0;}if(strncmp(msg,"QUIT",4) == 0)break;}if(msgctl(id,IPC_RMID,NULL) < 0){printf("del msg error \n");return 0;}return 0;
}

msgreceive()

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/msg.h>
#include<sys/ipc.h>
struct mymesg{long int mtype;char mtext[512];
};
int main()
{int id = 0;struct mymesg ckxmsg;key_t key = ftok("/tmp",66);id = msgget(key,0666|IPC_CREAT);if(id == -1){printf("open msg error \n");return 0;}while(1){if(msgrcv(id,(void *)&ckxmsg,512,1,0) < 0){printf("receive msg error \n");return 0;}printf("data:%s\n",ckxmsg.mtext);if(strncmp(ckxmsg.mtext,"QUIT",4) ==0)break;}return 0;
}


总结

优点:

消息队列的使用场景:解耦、异步、削峰。

缺点:

  • 系统的可用性降低
  • 系统复杂度提高
  • 一致性问题

特点:

  • 消息队列中的消息是有类型和格式的
  • 消息队列可以实现消息的随即查询—消息可具有优先级。
  • 消息队列允许一个或者多个进程向它写入或者读出数据
  • 与无名管道与命名管道一样,当消息被读取之后,它就会被消息队列删除
  • 每个消息队列具有一个消息队列标识符—系统唯一
  • 消息队列独立于进程存在,可无阻塞收发,只有内核重启或者人工删除消息队列时,消息队列才会被删除,否则会一直存在于系统中

http://www.ngui.cc/el/3882150.html

相关文章

opencv库 arm-linux交叉编译并移植

目录 静态链接库与动态链接库 opencv arm-linux交叉编译并移植 动态链接库与动态链接库 参考网址&#xff1a;https://www.jianshu.com/p/dbe848e4ad0d多个文件生成动态/静态库的用法&#xff1a; 动态库&#xff1a; 1.gcc -fPIC -shared xxx1.c xxx2.c xxx3.c -o libxxx…

列表中的一些命令

如何取出列表中第一个和最后一个元素 切片 1 names=["Xieningxuan","Huanglongjian","Changbingbing","Lifanlin","Fanlong","Zhangchi"] 2 print(names[0]) #列表中是第一个元素的位置是0, 也就是在列表中是…

八种数据类型详解——int篇

整数型在java中整数形式有四种表示形式: byte 1字节 -128————127 short 2字节 int 4字节 2147483647是int最大值,超了就用long long 8字节 最常用的是int 1字节=8比特 (8个二进制位) 在java 在任何情况下整数型的“字面量”默认被当作int类型处理 如果希望该“整数形…

textarea标签使用placeholder属性添加提示文字,页面却不显示

1.原因&#xff1a; 标签之间有空格 2.正确写法及错误写法&#xff1a; ①正确&#xff1a; <textarea type"text" name"message" placeholder"请输入留言内容"></textarea> ②错误&#xff1a; 2.1<textarea type"te…

微信小程序换行符、hidden属性、图片高度、背景图不显示问题

微信小程序问题总结 1. 换行符\n 1.1 微信小程序的换行符为\n,只能在text标签中使用&#xff0c;才能进行换行 1.2 当后端返回数据中存在换行符\n&#xff0c;并且在text标签中仍不进行换行显示&#xff0c;&#xff08;原因是后端返回的\n被转译成了字符串无法识别为换行符&a…

MatLab 2016b下载资源

MatLab 2016b下载资源 百度网盘链接: https://pan.baidu.com/s/1cc_qWaiLWl7O6e0O-xZjpg 提取码&#xff1a;6yei

并发控制(笔记)

并发控制 在单处理机系统中&#xff0c;事务的并发执行实际上是这些并行的事务的并行操作轮流交叉执行。 并发控制概述 事务是并发控制的基本单位 但是并发操作会出现以下一些问题。 1.丢失修改 2.不可重复读 3.读脏数据 &#xff08;与数据库内容不一致&#xff09; …

数据库恢复技术(笔记)

数据库恢复技术 1.事务 所谓事务是用户定义的一个数据库操作序列&#xff0c;这些操作要全做&#xff0c;要不全都不做&#xff0c;是一个不可分割的工作单位。 事务通常以BEGIN TRANSACTION开始&#xff0c;以commit或者是rollback结束&#xff0c; commit 表示提交&#xf…

查找的基本概念(笔记)

1.查找表 查找表是由同一类型的数据元素&#xff08;或记录&#xff09;构成的集合。 2.关键字 关键字是数据元素&#xff08;或记录&#xff09;中某个数据项的值。用它可以标识一个数据元素&#xff08;或记录&#xff09;。若此关键字可以唯一标识一个记录&#xff0c;则称此…

插入排序、冒泡排序、选择排序、快速排序(笔记+代码)

插入排序、冒泡排序、选择排序、快速排序 1.插入排序 插入排序&#xff0c;其实就像打牌一样&#xff0c;每一次从未添加数列中&#xff08;牌堆&#xff09;里面选取最近的一个关键字&#xff08;数&#xff09;&#xff0c;将这个数插入到已添加数列&#xff08;手牌&#…