IO进程线程(十一)进程间通信 消息队列

2024-06-10 19:28

本文主要是介绍IO进程线程(十一)进程间通信 消息队列,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、IPC(Inter-Process Communication)进程间通信相关命令 :
    • (一)ipcs --- 查看IPC对象
    • (二)获取IPC键值
    • (三)删除IPC对象的命令
    • (四)获取IPC键值的函数
      • 1. 函数定义
      • 2. 使用示例
  • 二、消息队列
    • (一) 特点
    • (二) 相关API
      • 1. 创建或获取一个消息队列
      • 2. 向消息队列中写消息
      • 3. 在消息队列中读取一条消息
      • 4. 控制消息队列
    • (三) 不关注消息类型
    • (四)关注消息类型
    • (五)消息队列属性结构体

一、IPC(Inter-Process Communication)进程间通信相关命令 :

(一)ipcs — 查看IPC对象

在这里插入图片描述

(二)获取IPC键值

ipcs -q 查看消息队列
在这里插入图片描述

ipcs -m 查看共享内存
在这里插入图片描述

ipcs -s 查看信号灯集
在这里插入图片描述

(三)删除IPC对象的命令

ipcrm -q id 删除消息队列
ipcrm -m id 删除共享内存
ipcrm -s id 删除信号灯集

(四)获取IPC键值的函数

1. 函数定义

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:使用给定的文件和字符来生成一个IPC通信的key值
参数:pathname:路径和文件名(必须是已存在的可访问的)proj_id:[0-255]的值  一般我们传一个字符即可 如 'A'  'm'
返回值:成功  key值失败  -1  重置错误码
  • 注:
  • typedef int key_t key_t 即 int 类型
  • pathname 要求必须是一个已存在的文件,因为key值的是由proj_id的后八位,设备号的后8位以及inode号的后16位组成。因此这种机制并不保证key值一定不重复
  • proj_id 只是用了int的一个字节,即取值范围是[0-255],一般使用时传一个字符,字符ASCII码是0-127。

2. 使用示例

生成并打印出key,分析key值的由来

验证代码

#include <my_head.h>int main(int argc, char const *argv[])
{key_t key=0;key = ftok("/home/linux/05work",'A');printf("my_key = %#x\n",key);struct stat file_stat= {0};stat("/home/linux/05work",&file_stat);printf("proj_id=%#x  dev=%#lx inode=%#lx\n",'A', file_stat.st_dev, file_stat.st_ino);return 0;
}

输出结果
在这里插入图片描述

二、消息队列

(一) 特点

  1. 消息队列也是基于内核实现的,存放在内存上(而非硬盘上)。
  2. 消息队列的大小,默认是 16K。
  3. 消息队列中的消息有类型和正文。
    A进程将消息写入消息队列;
    B进程可以根据消息的类型从消息队列中将对应类型的消息取走。
  4. 半双工通信

(二) 相关API

1. 创建或获取一个消息队列

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgget(key_t key, int msgflg);
功能:创建或者获取一个消息队列
参数:key:键值key 通过ftok获取的IPC_PRIVATE 表示只有亲缘进程间能用msgflg:消息队列的标志位IPC_CREAT|0666 消息队列不存在则创建,权限0666 或者  IPC_CREAT|IPC_EXCL|0666 消息不存在则创建,存在则但会-1,置错误码为EEXIST
返回值:成功 消息队列的id失败 -1 重置错误码

2. 向消息队列中写消息

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列中写入一条消息
参数:msqid:消息队列的idmsgp: 要写入的数据的首地址struct msgbuf {long mtype;      /* 消息的类型 必须大于 0 */char mtext[1];   /* 消息正文 可以自定义 */};msgsz:消息正文的大小msgflg:标志位 0 阻塞发送  IPC_NOWAIT 非阻塞发送
返回值:成功 0失败 -1  重置错误码
  • 注:
  • 关于void *msgp参数,第一个long类型大小的空间必须用来存放消息的类型,消息的正文可以自定义
  • size_t msgsz参数,只包含正文数据的大小(sizeof(struct msgbuf)-sizeof(mtype))
  • 阻塞发送的情况下,如果消息队列满了,A进程还想向消息队列中写入消息,此时A进程将会阻塞。

3. 在消息队列中读取一条消息

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能:在消息队列中读取一条消息
参数:msqid:消息队列的idmsgp: 用来保存接收的数据的缓冲区的首地址struct msgbuf {long mtype;     /* 消息的类型 必须大于 0 */char mtext[1];  /* 消息正文 可以自定义 */};msgsz:消息正文的大小msgtyp:要接受的消息的类型0 :接收消息队列中第一条消息>0 : 接收指定类型的第一条消息<0 :一般不使用,了解即可,表示接收消息队列中第一条类型最小的小于msgtyp的绝对值的消息3-2-5-500-200-8读取时,类型传 -200读取的顺序  2-3-5 msgflg:标志位 0 阻塞接收  IPC_NOWAIT 非阻塞接收
返回值:成功 实际读到的正文的字节数失败 -1  重置错误码
  • 注:读消息队列和写消息队列中的void *msgp结构体的内部成员要尽量对应。

4. 控制消息队列

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:控制消息队列
参数:msqid:消息队列idcmd:指令IPC_STAT:获取消息队列的属性IPC_SET:设置消息队列的属性IPC_RMID:立即删除消息队列只有消息队列的创建者和所有者以及root用户可以删除消息队列msgctl函数的第三个参数被忽略buff:属性结构体的地址
返回值:成功 0失败 -1  重置错误码

(三) 不关注消息类型

此时进程间通信不关心消息类型,按顺序接收第一条消息。
注意,当其中一个进程关闭消息队列后,另一个进程再试图关闭,就会报错,错误码EINVAL

read.c

#include <my_head.h>typedef struct msgbuf{long type;char name[20];
}msg_t;int main(int argc, char const *argv[])
{//获取key值key_t key=ftok("/home/linux/05work",'A');//创建消息队列int msgid = msgget(key,IPC_CREAT|0666);if(-1 == msgid)ERR_LOG("msgget error");//定义消息结构体msg_t msg={0};int type=0;while(1){if(-1 == msgrcv(msgid,&msg,sizeof(msg_t)-sizeof(msg.type),type,0)){ERR_LOG("msgrcv error");}if(!strcmp(msg.name,"quit")){break;}printf("%ld:%s\n",msg.type,msg.name);}if(-1 == msgctl(msgid,IPC_RMID,NULL)){if(EINVAL == errno){return 0;}ERR_LOG("msgctl error");}return 0;
}

write.c

#include <my_head.h>typedef struct msgbuf{long type;char name[20];
}msg_t;int main(int argc, char const *argv[])
{//获取key值key_t key=ftok("/home/linux/05work",'A');//创建消息队列int msgid = msgget(key,IPC_CREAT|0666);if(-1 == msgid)ERR_LOG("msgget error");//定义消息结构体msg_t msg={0};while(1){printf("请输入消息:<类型> <正文>:");scanf("%ld %s",&msg.type,msg.name);msgsnd(msgid,&msg,sizeof(msg_t)-sizeof(msg.type),0);if(!strcmp(msg.name,"quit")){break;}}//销毁消息队列if(-1 == msgctl(msgid,IPC_RMID,NULL)){if(EINVAL == errno){return 0;}ERR_LOG("msgctl error");}return 0;
}

(四)关注消息类型

此时进程间通信关心消息类型,按顺序接收第一条符合类型的消息,如果消息队列中没有同类型消息,会阻塞等待。
在这里插入图片描述
read.c

#include <my_head.h>typedef struct msgbuf{long type;char name[20];
}msg_t;int main(int argc, char const *argv[])
{//获取key值key_t key=ftok("/home/linux/05work",'A');//创建消息队列int msgid = msgget(key,IPC_CREAT|0666);if(-1 == msgid)ERR_LOG("msgget error");//定义消息结构体msg_t msg={0};int type=0;while(1){printf("请输入想要接收的消息类型:");scanf("%d",&type);if(-1 == msgrcv(msgid,&msg,sizeof(msg_t)-sizeof(msg.type),type,0)){ERR_LOG("msgrcv error");}if(!strcmp(msg.name,"quit")){break;}printf("%ld:%s\n",msg.type,msg.name);}if(-1 == msgctl(msgid,IPC_RMID,NULL)){if(EINVAL == errno){return 0;}ERR_LOG("msgctl error");}return 0;
}

write.c

#include <my_head.h>typedef struct msgbuf{long type;char name[20];
}msg_t;int main(int argc, char const *argv[])
{//获取key值key_t key=ftok("/home/linux/05work",'A');//创建消息队列int msgid = msgget(key,IPC_CREAT|0666);if(-1 == msgid)ERR_LOG("msgget error");//定义消息结构体msg_t msg={0};while(1){printf("请输入消息:<类型> <正文>:");scanf("%ld %s",&msg.type,msg.name);msgsnd(msgid,&msg,sizeof(msg_t)-sizeof(msg.type),0);if(!strcmp(msg.name,"quit")){break;}}//销毁消息队列if(-1 == msgctl(msgid,IPC_RMID,NULL)){if(EINVAL == errno){return 0;}ERR_LOG("msgctl error");}return 0;
}

(五)消息队列属性结构体

struct msqid_ds {struct ipc_perm msg_perm;     /* IPC权限结构体 */time_t          msg_stime;    /* 最后一次执行msgsnd的时间 */time_t          msg_rtime;    /* 最后一次执行msgrcv的时间 */time_t          msg_ctime;    /* 最后一次被修改的时间 */unsigned long   __msg_cbytes; /* 当前消息队列中的字节数 */msgqnum_t       msg_qnum;     /* 当前消息队列中的消息数 */msglen_t        msg_qbytes;   /* 允许的最大字节数 */pid_t           msg_lspid;    /* 最后一次执行msgsnd的进程的PID */pid_t           msg_lrpid;    /* 最后一次执行msgrcv的进程的PID */
};struct ipc_perm {key_t          __key;       /* 键值 */uid_t          uid;         /* 所属用户的id */gid_t          gid;         /* 所属用户的组id */uid_t          cuid;        /* 创建者的id */gid_t          cgid;        /* 创建者的组id */unsigned short mode;        /* 权限 */
};
  • 注:
    qbytes可以改小,改大的话需要sudo权限

这篇关于IO进程线程(十一)进程间通信 消息队列的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1049049

相关文章

Android SurfaceFlinger——图形内存分配器(十一)

前面的文章中的图层合成器(HWC),这里我们接着看一下 SurfaceFlinger 中的另一个重要服务——图形内存分配器。 一、简介         android.hardware.graphics.allocator@2.0 是 Android 系统中硬件抽象层(HAL)的一个组件,专门用于图形内存的分配和管理。它是 SurfaceFlinger 在处理图形数据时所依赖的

消息认证码解析

1. 什么是消息认证码         消息认证码(Message Authentication Code)是一种确认完整性并进行认证的技术,取三个单词的首字母,简称为MAC。         消息认证码的输入包括任意长度的消息和一个发送者与接收者之间共享的密钥,它可以输出固定长度的数据,这个数据称为MAC值。         根据任意长度的消息输出固定长度的数据,这一点和单向散列函数很类似

IPD推行成功的核心要素(十一)技术规划与平台规划促进公司战略成功

随着外部大环境的影响,各企业仅有良好的愿望是不够的。预测并顺应新兴市场和技术的变化,变危机为转机,不断推出强大的产品才是一个公司持续繁荣的根本保障。而高效的产品开发往往是基于某些关键技术,针对市场推出的一个或几个产品系列,这些产品系列通常共用一些产品平台,共用一种或者几种关键技术。当一家企业进入了平稳发展期,已经建立了较为完善的管理制度和产品开发流程,但是依然认为竞争对手是那样强大,那样不可战胜。

Java线程面试题(50)

不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员的欢迎。大多数待遇丰厚的Java开发职位都要求开发者精通多线程技术并且有丰富的Java程序开发、调试、优化经验,所以线程相关的问题在面试中经常会被提到。 在典型的Java面试中, 面试官会从线程的基本概念问起, 如:为什么你需要使用线程,

【数据结构与算法 经典例题】使用队列实现栈(图文详解)

💓 博客主页:倔强的石头的CSDN主页               📝Gitee主页:倔强的石头的gitee主页    ⏩ 文章专栏:《数据结构与算法 经典例题》C语言                                   期待您的关注 ​​ 目录  一、问题描述 二、前置知识 三、解题思路 四、C语言实现代码 🍃队列实现代码:

Java——IO流(一)-(5/8):IO流概述、字节流-FileInputStream 每次读取一个字节

IO流概述 介绍 输入输出流,用于读写数据。 I指Input,称为输入流:负责把数据读到内存中去。 O指Output,称为输出流:负责写数据出去。 IO流的应用场景 文件内容的读写永久保存应用数据复制粘贴对话通信等等 怎么学IO流 理清楚IO六点分类和体系循序渐进、深入学习每个IO流的作用和用法 IO流的分类 IO流总体来看就有四大类: 字节输入流:以内存

RabbitMQ实践——临时队列

临时队列是一种自动删除队列。当这个队列被创建后,如果没有消费者监听,则会一直存在,还可以不断向其发布消息。但是一旦的消费者开始监听,然后断开监听后,它就会被自动删除。 新建自动删除队列 我们创建一个名字叫queue.auto.delete的临时队列 绑定 我们直接使用默认交换器,所以不用创建新的交换器,也不用建立绑定关系。 实验 发布消息 我们在后台管理页面的默认交换器下向这个队列

Java并发编程—阻塞队列源码分析

在前面几篇文章中,我们讨论了同步容器(Hashtable、Vector),也讨论了并发容器(ConcurrentHashMap、CopyOnWriteArrayList),这些工具都为我们编写多线程程序提供了很大的方便。今天我们来讨论另外一类容器:阻塞队列。   在前面我们接触的队列都是非阻塞队列,比如PriorityQueue、LinkedList(LinkedList是双向链表,它实现了D

线程池ThreadPoolExecutor类源码分析

Java并发编程:线程池的使用   在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:   如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。   那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

线程Lock

线程Lock   在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问。本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock。   也许有朋友会问,既然都可以通过synchronized来实现同步访问了,那么为什么还需要提供Lock?这个问题将在下面进行阐述。本文先从s