Linux:利用System V系列的-共享内存,消息队列实现进程间通信

        对于管道的进程间通信方式,需要频繁的调用系统调用(read,write)。而我们今天首先要介绍的共享内存,在开辟好空间之后,便可以跳过系统调用,直接进行读写操作。

一.System V共享内存(主要)

        共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

        所以,共享内存就像我们malloc的一块空间一样,指出地址即可直接访问,不需要使用任何的系统调用:

1.1几个主要的共享内存函数 

ftok函数 

key_t ftok(const char *pathname, int proj_id)参数:
pathname:一个已存在的文件路径。ftok 会使用该文件的 inode 号和设备号来生成键值。
proj_id:一个 8 位的项目标识符(通常是一个字符,范围是 0 到 255)。用于在同一文件路径下生成不同的键值。返回值:
成功时返回生成的键值(key_t 类型)。
失败时返回 -1,并设置 errno 为相应的错误代码。注意事项
文件存在:pathname 必须指向一个已存在的文件,否则 ftok 会失败。
唯一性:不同的 pathname 和 proj_id 组合通常会生成不同的键值,但在某些情况下(如文件系统更改)可能会产生冲突。
可移植性:ftok 生成的键值在不同系统上可能不同,因此在跨平台应用中使用时需谨慎。

shmget函数 

功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。
取值为IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,出
错返回。
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

 shmat函数

功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

说明:

shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。
公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数 

功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数 

功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

cmd:

IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID:删除共享内存段

 1.2实现一个基于共享内存的进程间通信

        主体思路:共享内存虽然可以快速写入和读取数据,但这也是它的缺点。无法确保数据同步,可能会出现客户端在写,还没写完服务端就读取的情况。我们这里则用命名管道,当客户端写完消息后,发送消息给服务端,告知数据写入完毕再让服务端进行读取,也就达到了我们数据同步的目的。

实现命名管道的文件Fifo.hpp:

#pragma once
#include "comm.hpp"#define PATH "."
#define FILENAME "fifo"class NamedFifo
{
public:NamedFifo(const std::string &path, const std::string &name): _path(path), _name(name){_fifoname = _path + "/" + _name;umask(0);// 新建管道int n = mkfifo(_fifoname.c_str(), 0666);if (n < 0){ERR_EXIT("mkfifo");}else{std::cout << "mkfifo success" << std::endl;}}~NamedFifo(){// 删除管道文件int n = unlink(_fifoname.c_str());}private:std::string _path;std::string _name;std::string _fifoname;
};class FileOper
{
public:FileOper(const std::string &path, const std::string &name): _path(path), _name(name), _fd(-1){_fifoname = _path + "/" + _name;}void OpenForRead(){// 打开, write 方没有执行open的时候,read方,就要在open内部进行阻塞// 直到有人把管道文件打开了,open才会返回!_fd = open(_fifoname.c_str(), O_RDONLY);if (_fd < 0){ERR_EXIT("open");}std::cout << "open fifo success" << std::endl;}void OpenForWrite(){// write_fd = open(_fifoname.c_str(), O_WRONLY);if (_fd < 0){ERR_EXIT("open");}std::cout << "open fifo success" << std::endl;}void Write(){write(_fd, message.c_str(), message.size());}void Read(){char buffer[1024];int number = read(_fd, buffer, sizeof(buffer) - 1);if (number > 0){buffer[number] = 0;std::cout << "Client Say# " << buffer << std::endl;}else if (number == 0){std::cout << "client quit! me too!" << std::endl;}else{std::cerr << "read error" << std::endl;}}void Close(){if (_fd > 0)close(_fd);}~FileOper(){}private:std::string _path;std::string _name;std::string _fifoname;int _fd;
};

实现共享内存接口的文件shm.hpp:

#include "comm.hpp"const int gdefaultid = -1;
const int gsize = 4096;
const std::string pathname = ".";
const int projid = 0x64;
const int gmod = 0666;const std::string Creater = "Creater";
const std::string User = "User";class Shm
{void CreatHelper(int flag){key_t k = ftok(pathname.c_str(),projid);if(k < 0){ERR_EXIT("ftok");}_key = k;_shmid = shmget(k,_size,flag);if(_shmid < 0){ERR_EXIT("shmget");}std::cout << "shmid:" << _shmid << std::endl;}void Attach(){_start_mem = shmat(_shmid,nullptr,0);if((long long)_start_mem < 0){ERR_EXIT("shmat");}printf("attach success\n");}void Creat(){CreatHelper(IPC_CREAT | IPC_EXCL | gmod);}void Detach(){int n = shmdt((char*)_start_mem);if(n == 0)printf("detach success\n");}void Get(){CreatHelper(IPC_CREAT);}void DestoryShm(){Detach();int n = shmctl(_shmid,IPC_RMID,nullptr);if(n == 0){printf("delete mem success\n");}else{ERR_EXIT("shmctl");}}
public:Shm(const std::string& username):_shmid(gdefaultid),_size(gsize),_username(username){if(_username == Creater){Creat();}else Get();Attach();}void* VirtualAddr(){//printf("VirtalAddr: %p\n",_start_mem);return _start_mem;}~Shm(){Detach();if(_username == Creater){DestoryShm();}}
private:int _shmid;key_t _key;int _size;std::string _username;void* _start_mem;
};

二者的公共部分comm.hpp

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define ERR_EXIT(m)         \do                      \{                       \perror(m);          \exit(EXIT_FAILURE); \} while (0)

服务端server.cc

#include "Shm.hpp"
#include "Fifo.hpp"
int main()
{Shm _sh(Creater);NamedFifo fifo(PATH,FILENAME);FileOper o_fifo(PATH,FILENAME);o_fifo.OpenForRead();o_fifo.Read();std::cout << (char*)_sh.VirtualAddr() << std::endl;sleep(10);o_fifo.Close();return 0;
}

客户端client.cc

#include "Shm.hpp"
#include "Fifo.hpp"
int main()
{Shm _sh(User);FileOper o_fifo(PATH,FILENAME);o_fifo.OpenForWrite();const char* str = "这是向服务端发送的一条消息:I am a process.";char* mem = (char*)_sh.VirtualAddr();int i = 0;for(;str[i];i++){mem[i] = str[i];}mem[i] = 0;o_fifo.Write();sleep(5);return 0;
}

 运行后效果如下:

当然也可以自己输入消息让服务端接收,实现自定义消息发送。

二.System V消息队列与信号量(了解)

        由于System V这种通信方式我们已经很少使用的问题,所以我们只需要了解共享内存即可,消息队列与信号量实现进程间通信的方式与共享内存几乎一致(只是变了个函数名)。这里我们只给出消息队列版本进程间通信的实现以及相关函数的介绍,信号量读者可自行了解相关函数进行实现。

2.1消息队列的相关主要函数 

msgget函数

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);参数
• key : 某个消息队列的名字
• msgflg :由九个权限标志构成,它们的⽤用法和创建⽂文件时使⽤用的mode模式标志是⼀样的
返回值
• 成功返回一个非负整数,即该消息队列的标识码;失败返回-1

msgctl函数 

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);msgid : 由msgget 函数返回的消息队列标识码
cmd :将要采取的动作(有三个可取值),分别如下:
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID:删除共享内存段buf : 属性缓冲区返回值
成功返回0;失败返回-1

msgsnd函数 

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);参数
msgid : 由msgget 函数返回的消息队列标识码
msgp:是一个指针,指针指向准备发送的消息
msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgflg:控制着当前消息队列满或到达系统上限时将要发⽣生的事情, 0即可
( msgflg=IPC_NOWAIT 表⽰示队列满不等待,返回EAGAIN 错误 )。返回值
成功返回0;失败返回-1关于消息主体
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
// 以一个long int长整数开始,接收者函数将利⽤用这个长整数确定消息的类型

msgrcv函数 

#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);参数
msgid : 由msgget 函数返回的消息队列标识码
msgp :是一个指针,指针指向准备接收的消息
msgsz :是msgp 指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgtype :它可以实现接收消息的类型,也可以模拟优先级的简单形式进行接收
msgflg :控制着队列中没有相应类型的消息可供接收时将要发⽣生的事返回值
成功返回实际放到接收缓冲区⾥里去的字符个数,失败返回-1msgflg标志位-了解
msgtype=0返回队列第一条信息
msgtype>0返回队列第一条类型等于msgtype的消息 
msgtype<0返回队列第一条类型小于等于msgtype绝对值的消息,并且是满⾜足条件的消息类型最小
的消息
msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误。
msgflg=MSG_NOERROR,消息大小超过msgsz时被截断
msgtype>0且msgflg=MSG_EXCEPT,接收6 类型不等于msgtype的第一条消息

2.2消息队列实现进程间通信 

MsgQueue.hpp 

#ifndef MSGQUEUE_HPP
#define MSGQUEUE_HPP
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <cstring>const int default_msgid = -1;
const int MSG_FLAG_SERVER = IPC_CREAT | IPC_EXCL | 0666;
const int MSG_FLAG_CLIENT = IPC_CREAT;
const int MSG_SIZE = 1024;#define PATHNAME "/home/ly/My_Linux_Code/lesson17/MsgQueen"
#define MSG_TYPE_Server 1
#define MSG_TYPE_Client 2
#define project_id 1234class MsgQueue {struct msgbuf {long mtype;char mtext[MSG_SIZE];};
public:MsgQueue(int msgid = default_msgid): _msgid(msgid) {}void Creat(int _msgflg){key_t key = ftok(PATHNAME, project_id);if(key == -1){std::cerr << "ftok error" << std::endl;exit(1);}_msgid = msgget(key, _msgflg);if(_msgid == -1){std::cerr << "msgget error" << std::endl;exit(2);}std::cout << "msgget create success: " << _msgid << std::endl;}void send(int type, std::string& text){struct msgbuf msg;memset(&msg, 0, sizeof(msg));msg.mtype = type;memcpy(msg.mtext, text.c_str(), text.size());int n = msgsnd(_msgid, &msg, MSG_SIZE, 0);if(n == -1){std::cerr << "msgsnd error" << std::endl;exit(4);}}void recv(int type, std::string& text){struct msgbuf msg;memset(&msg, 0, sizeof(msg));int n = msgrcv(_msgid, &msg, MSG_SIZE, type, 0);if(n == -1){std::cerr << "msgrcv error" << std::endl;exit(5);}text = msg.mtext;text[n] = '\0';}void Destroy(){int n = msgctl(_msgid, IPC_RMID, 0);if(n == -1){std::cerr << "msgqueue destroy error" << std::endl;exit(3);}}~MsgQueue() {}   
private:int _msgid;
};class server : public MsgQueue {
public:server() {MsgQueue::Creat(MSG_FLAG_SERVER);}~server() {MsgQueue::Destroy();}
};class client : public MsgQueue {
public:client() {MsgQueue::Creat(MSG_FLAG_CLIENT);}~client() {}
};
#endif

 客户端代码client.cc

#include "MsgQueue.hpp"int main() {client c;std::string msg;while (true) {std::cin >> msg;c.send(MSG_TYPE_Client, msg);if (msg == "exit") { break;}}return 0;
}

服务端代码server.cc 

#include "MsgQueue.hpp"
#include "ChainOfRespons.hpp"int main() {server s;std::string text;HandlerEntry he;while(true){s.recv(MSG_TYPE_Client, text);std::cout << "Client says: " << text << std::endl;if(text == "exit"){break;}//责任链模式处理数据//he.Handle(text);}return 0;
}

 

 

 

 

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

不像人做的题————十四届蓝桥杯省赛真题解析(上)A,B,C,D题解析

题目A&#xff1a;日期统计 思路分析&#xff1a; 本题的题目比较繁琐&#xff0c;我们采用暴力加DFS剪枝的方式去做&#xff0c;我们在DFS中按照8位日期的每一个位的要求进行初步剪枝找出所有的八位子串&#xff0c;但是还是会存在19月的情况&#xff0c;为此还需要在CHECK函数…

宇树人形机器人开源模型

1. 下载源码 https://github.com/unitreerobotics/unitree_ros.git2. 启动Gazebo roslaunch h1_description gazebo.launch3. 仿真效果 H1 GO2 B2 Laikago Z1 4. VMware: vmw_ioctl_command error Invalid argument 这个错误通常出现在虚拟机环境中运行需要OpenGL支持的应用…

【C/C++算法】从浅到深学习--- 前缀和算法(图文兼备 + 源码详解)

绪论&#xff1a;冲击蓝桥杯一起加油&#xff01;&#xff01; 每日激励&#xff1a;“不设限和自我肯定的心态&#xff1a;I can do all things。 — Stephen Curry” 绪论​&#xff1a; 本章将使用八道题由浅到深的带你了解并基本掌握前缀和思想&#xff0c;以及前缀和的基…

脑电:时域分析(任务态)

时域分析&#xff1a;时间序列&#xff08;时域信号&#xff09; EEG和ERP都是时间序列 ERP&#xff1a;事件诱发的电位是随着时间变化 组水平&#xff1a;需要这一组的个体不能差异性太大。 提值的指标&#xff0c;选取平均幅值确定成分的显著情况 mean(EEG.data,3): 在第…

【C语言】自定义类型:结构体,联合,枚举(下)

前言&#xff1b;上一期我们侧重讲了一个非常重要的自定义类型结构体&#xff0c;这一期我们来说说另外两种自定义类型&#xff1a;联合&#xff0c;和枚举。 传送门&#xff1a;自定义类型&#xff1a;结构体&#xff0c;联合&#xff0c;枚举(上) 文章目录 一&#xff0c;联…

数组的介绍

1.数组的概念 数组是一组相同类型元素的集合&#xff0c;从这个描述中我们知道&#xff1a; 数组中存放1个或多个数据&#xff0c;但是数组的元素个数不为0。数组中存放的多个数据&#xff0c;类型是相同的。 数组分为一维数组和多维数组&#xff0c;多维数组一般比较多见的…

蓝桥杯 17110抓娃娃

问题描述 小明拿了 n 条线段练习抓娃娃。他将所有线段铺在数轴上&#xff0c;第 i 条线段的左端点在 li&#xff0c;右端点在 ri​。小明用 m 个区间去框这些线段&#xff0c;第 i个区间的范围是 [Li​, Ri​]。如果一个线段有 至少一半 的长度被包含在某个区间内&#xff0c;…

linux ptrace 图文详解(二) PTRACE_TRACEME 跟踪程序

目录 一、基础介绍 二、PTRACE_TRACE 实现原理 三、代码实现 四、总结 &#xff08;代码&#xff1a;linux 6.3.1&#xff0c;架构&#xff1a;arm64&#xff09; One look is worth a thousand words. —— Tess Flanders 一、基础介绍 GDB&#xff08;GNU Debugger&…

记录致远OA服务器硬盘升级过程

前言 日常使用中OA系统突然卡死&#xff0c;刷新访问进不去系统&#xff0c;ping服务器地址正常&#xff0c;立马登录服务器检查&#xff0c;一看磁盘爆了。 我大脑直接萎缩了&#xff0c;谁家OA系统配400G的空间啊&#xff0c;过我手的服务器没有50也是30台&#xff0c;还是…

电网电压暂态扰动机理与工业设备抗失压防护策略研究

什么是晃电&#xff1f; 国标GB/T 30137-2013 中定义:工频电压方均根值突然降至额定值的90%~10%&#xff0c;持续时间为10ms~1min后恢复正常的现象。Acrel8757V 晃电的原因 1.系统侧因素 短路故障&#xff1a;雷击、线路接地、设备误碰等导致电网短路&#xff0c;故障点电压…

Linux监控网络状态

一、基本介绍 1、基本语法 netstat [选项] 2、常用选项 选项 说明 -a 显示所有连接和监听的套接字&#xff08;包括TCP、UDP&#xff09;。 -t 显示 TCP 连接。 -u 显示 UDP 连接。 -l 显示正在监听的套接字&#xff08;server端&#xff09;。 -n 显示数字格式的…

UE5以插件的形式加载第三方库

之前在UE中加载第三方库的形式是以静态或者动态链接的形式加载但是不太容易复用。就想着能不能以插件的形式加载第三方库&#xff0c;这样直接把插件打包发行就可以复用了&#xff0c;之前也找过相应的教程但是很难找到比较简单易懂的教程&#xff0c;要么是比较复杂&#xff0…

Go执行当前package下的所有方法

需求&#xff1a;需要一个文件一个定时任务方法&#xff0c;当项目初始化完毕后&#xff0c;自动加载并执行这些定时任务方法 项目目录架构 main.go 初始化 package mainimport ("sql_demo/schedule" )func main() {/***** 其他初始化完毕后的操作**/// 定时任务sc…

AnyAnomaly: 基于大型视觉语言模型的零样本可定制视频异常检测

文章目录 速览摘要1. 引言2. 相关工作视频异常检测大型视觉语言模型&#xff08;LVLMs&#xff09; 3. 方法3.1. 总览3.2. 关键帧选择模块3.3. 上下文生成基于 WinCLIP 的注意力机制网格图像生成 3.4. 异常检测提示词设计异常评分 4. 实验4.1. 数据集4.2. 评估标准4.3. 结果4.4…

【AWS入门】2025 AWS亚马逊云科技账户注册指南

【AWS入门】2025 AWS亚马逊云科技账户注册指南 A Guide To Register a New account on AWS By JacksonML 0. AWS亚马逊云科技简介 Amazon Web Service(AWS) 即亚马逊云科技&#xff0c;其在全球Cloud Computing(云计算)市场占有最为重要的地位。 AWS连续13年被Gartner评为…

Spring 中 SmartInitializingSingleton 的作用和示例

一、 接口定义 SmartInitializingSingleton 是 Spring 框架提供的一个 单例 Bean 全局初始化回调接口&#xff0c;用于在 所有非延迟单例 Bean 初始化完成后 执行自定义逻辑。 核心方法&#xff1a; public interface SmartInitializingSingleton {void afterSingletonsInsta…

element tree树形结构默认展开全部

背景&#xff1a; el-tree树形结构&#xff0c;默认展开全部&#xff0c;使用属性default-expand-all【是否默认展开所有节点】&#xff1b;默认展开一级&#xff0c;设置default-expanded-keys【默认展开的节点的 key 的数组】属性值为数组。 因为我这里的数据第一级是四川【省…

大数据-spark3.5安装部署之local模式

spark&#xff0c;一个数据处理框架和计算引擎。 下载 local模式即本地模式&#xff0c;就是不需要任何其他节点资源就可以在本地执行spark代码的环境。用于练习演示。 上传解压 使用PortX将文件上传至/opt 进入/opt目录&#xff0c;创建目录module&#xff0c;解压文件至/o…

Discuz建站教程之论坛头部logo跳转链接怎么修改?

在修改头部logo跳转链接前&#xff0c;我们需要知道对应代码在哪个文件目录&#xff0c;进入宝塔或是服务器&#xff0c;找到文件&#xff1a;\template\default\common\header.htm&#xff0c;编辑器打开&#xff0c;搜索以下代码&#xff0c;大概在135行 <a href"{i…

【FreeRTOS】FreeRTOS操作系统在嵌入式单片机上裸机移植

目录 一 RTOS概述 二 FreeRTOS移植 三 FreeRTOS使用 四 附录 一 RTOS概述 先了解一些基础概念,以下内容摘自FreeRTOS官网(FreeRTOS™ - FreeRTOS™): 【1】RTOS基础知识 实时操作系统 (RTOS) 是一种体积小巧、确定性强的计算机操作系统。 RTOS 通常用于需要在严格时间限…