使用共享内存进行通信的代码和运行情况分析,共享内存的特点(拷贝次数,访问控制),加入命名管道进行通信的代码和运行情况分析

news2025/1/17 13:58:40

目录

示例代码

头文件(comm.hpp)

log.hpp

基础版 -- 服务端

代码

运行情况

加入客户端

代码

运行情况

两端进行通信 

客户端

代码

注意点

服务端

代码

两端运行情况

共享内存特点

拷贝次数少

管道的拷贝次数

共享内存的拷贝次数

没有访问控制

管道

共享内存

并发问题

添加访问控制(通过管道)

代码

头文件

服务端

客户端

运行情况


我们已经介绍了共享内存多个接口的使用,接下来就开始实际调用一下吧

示例代码

头文件(comm.hpp)

#ifndef COMM_H
#define COMM_H

#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "log.hpp"
#include <assert.h>

using namespace std;

#define PATH_NAME "/home/mufeng"
#define PROJ_ID 0x1234
#define SUM_SIZE 4096

#endif

log.hpp

我们将不同的报错信息分成4种等级

#pragma once

#include <iostream>
#include <string>
#include <time.h>

using namespace std;

#define debug 0
#define notice 1
#define warning 2
#define error 3

const string msg[]{
    "debug", "notice", "warning", "error"};

ostream &log(string message, int level)
{
    cout << "|" << (unsigned)time(nullptr) << "|" << message << "|" << msg[level] << "|";
    return cout;
}

基础版 -- 服务端

代码

注意我们使用assert来确保调用成功,并且成功后就进行日志打印

#include"comm.hpp"

int main(){
    key_t key = ftok(PATH_NAME, PROJ_ID);
    assert(key != -1);
    (void)key;
    log("key created success", debug) << ", key : " << key << endl;

    int shmid = shmget(key, SUM_SIZE, IPC_CREAT|IPC_EXCL|0666);
    assert(shmid != -1);
    (void)shmid;
    log("shm created success", debug) << ", shmid : " << shmid << endl;
    sleep(3);

    char *addres = (char *)shmat(shmid, nullptr, 0);
    log("process link success", debug) << endl;
    sleep(3);

    int ret = shmdt(addres);
    assert(ret != -1);
    (void)ret;
    log("process unlink success", debug) << endl;
    sleep(3);

    ret = shmctl(shmid, IPC_RMID, nullptr);
    assert(ret != -1);
    (void)ret;
    log("shm unlink success", debug) << endl;
    sleep(3);

    return 0;
}

运行情况

监控共享内存的使用情况(while :;do ipcs -m ;sleep 1;done):

  • 连接数从0 -> 1
  • 解除连接后,连接数从1 -> 0
  • 删除后,共享内存块消失

 

加入客户端

代码

相似的操作

但我们的客户端不需要创建新的共享内存,而是使用服务端使用的那个:

int main()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key < 0)
    {
        log("key created failed", error) << ", client key : " << key << endl;
    }

    int shmid = shmget(key, SUM_SIZE, 0);
    if (shmid < 0)
    {
        log("shmid created failed", error) << ", client shmid : " << shmid << endl;
    }

    char *addres = (char *)shmat(shmid, nullptr, 0);
    if (addres == nullptr)
    {
        log("process link failed", error) << endl;
    }
    sleep(3);

    int ret = shmdt(addres);
    if (ret < 0)
    {
        log("process unlink failed", error) << endl;
    }
    sleep(3);

    //这里不需要删除,服务端会将这块内存释放掉

    return 0;
}

运行情况

连接数从0 -> 1 -> 2 -> 1 -> 0:

 

两端进行通信 

客户端

  • 客户端一般是发送内容给服务端
  • 这里我们将从标准输入(也就是键盘)读入的内容,写入到addres中
  • 如果读到了quit,就退出
代码
//通信
    while(true){
        ssize_t size=read(0,addres,SUM_SIZE-1);
        if(size>0){
            addres[size-1]=0;
            if(strcmp(addres,"quit")==0){
                break;
            }
        }
    }
注意点
  • 我们在输入时,实际上会将按的回车也读入
  • 但我们判断的是"quit",它不包括换行符
  • 所以需要将addres从读入的那个换行符开始,设置为0

服务端

  • 从addres中读取数据
  • 这里我们直接将内容打印
  • 如果读到了quit,就退出
代码

while (true){
        if (strcmp(addres, "quit") == 0)
        {
            break;
        }
        cout << addres << endl;
        sleep(2);
    }

 

两端运行情况

 

共享内存特点

这里我们使用命名管道作为对比的例子,之后会使用管道来完善共享内存

拷贝次数少

管道的拷贝次数

  • 通过管道通信 -- 也就是创建两个文件作为管道的读端和写端
  • 当写入的时候,我们通过键盘输入,输入的数据先被拷贝到我们自己设定的缓冲区(也就是定义的数组)中,然后再被传输到管道文件中
  • 读出也是一样,先要从管道文件到设定的缓冲区,再打印出来,而打印也就是将数据传输到显示器上
  • 所以至少需要四次拷贝

   

共享内存的拷贝次数

  • 共享内存是直接在物理内存上开辟一块空间,然后映射到需要进行通信的进程的地址空间中
  • 写入的时候,输入的内容实际上是直接写入到共享内存中的,不需要经过自定义的缓冲区
  • 打印也同样,直接从共享内存中读出,然后显示到显示器上
  • 所以只需要两次

 

没有访问控制

管道

  • 前面已经操作过了,管道文件只有当双方同时打开时,才会开始通信,否则会阻塞
  • 写满 / 没有写,另一方会等待,而不是一直在读

共享内存

  • 没有任何的控制
  • 从前面的操作可以看到,其中一方的运行不需要依赖另一方
  • 只要写完一句,就直接会被读走
  • 即使没有写,也会一直读 
  • 这样就会导致并发问题
并发问题
  • 可能要传递的信息是很长的,但可能中途就会被服务端读走
  • 这样它就拿不到完整的数据,可能就会导致无法执行相应的操作

添加访问控制(通过管道)

因为管道是有访问控制的,所以可以借助管道,让共享内存也具有访问控制

代码
头文件
// 加入访问控制(通过管道来传递信号,接收到信号才进行读取)

#define FIFO_PATH "./fifo"
#define READ O_RDONLY
#define WRITE O_WRONLY | O_TRUNC

class Init //让管道文件具有类的特性,出作用域自动释放
{
public:
    Init()
    {
        umask(0);
        int ret = mkfifo(FIFO_PATH, 0666);
        assert(ret == 0);
        (void)ret;
        log("fifo created success", notice) << endl;
    }
    ~Init()
    {
        unlink(FIFO_PATH);
        log("fifo removed success", notice) << endl;
    }
};

void wait_signal(int fd) //读取指定文件内容作为信号
{
    uint32_t signal = 0;
    log("waiting ...", notice) << endl;
    ssize_t size = read(fd, &signal, sizeof signal);
    assert(size == sizeof(uint32_t));
    (void)size;
}
void send_signal(int fd) //向指定文件写入signal
{
    uint32_t signal = 1;
    ssize_t size = write(fd, &signal, sizeof signal);
    assert(size == sizeof(uint32_t));
    (void)size;
    log("being awakened ...", notice) << endl;
}

int open_fifo(string path, int flags) //以指定方式打开创建好的管道文件
{
    int fd = open(path.c_str(), flags);
    assert(fd >= 0);
    return fd;
}
void close_fifo(int fd)
{
    close(fd);
}
服务端
  • 创建管道文件
  • 等待客户端的信号(也就是等待管道文件中出现内容时)
  • 被唤醒后打印addres中的内容
//通信
// 添加访问控制
    Init init; // 创建管道文件
    int fd = open_fifo(FIFO_PATH, READ);
    while (true)
    {
        wait_signal(fd); // 等待唤醒
        if (strcmp(addres, "quit") == 0)
        {
            break;
        }
        cout << addres << endl;
    }
    close_fifo(fd); // 通信结束
客户端
  • 打开创建好的管道文件
  • 读取键盘输入内容,存入addres中
  • 成功输入时,向服务端发送信号(也就是向管道写入数据)
 // 添加访问控制
    int fd = open_fifo(FIFO_PATH, WRITE);
    while (true)
    {
        ssize_t size = read(0, addres, SUM_SIZE - 1);
        if (size > 0)
        {
            addres[size - 1] = 0; //处理回车符
            send_signal(fd);
            if (strcmp(addres, "quit") == 0)
            {
                break;
            }
        }
    }

 

运行情况

只有一方时,阻塞在管道文件打开的位置:

当客户端接入后:

发送信息时,会将信号和数据都传递给对方:

退出:

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

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

相关文章

三十、W5100S/W5500+RP2040树莓派Pico<PPPoE>

文章目录 1 前言2 简介2 .1 什么是PPPoE&#xff1f;2.2 PPPoE的优点2.3 PPPoE数据交互原理2.4 PPPOE应用场景 3 WIZnet以太网芯片4 PPPOE示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接 1 前言 PPPoE是一种在以太…

【机器学习 | 假设检验】那些经常被忽视但重要无比的假设检验!! 确定不来看看?(附详细案例)

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

代码随想录算法训练营Day 54 || 392.判断子序列、115.不同的子序列

392.判断子序列 力扣题目链接(opens new window) 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;&quo…

STM32-基本定时器

一、基本定时器的作用 定时触发输出直接驱动DAC。 二、基本定时器的框图 以STM32F103系列为例&#xff0c;具体开发板请查看开发手册。 类别定时器总线位数计数方向预分频系数是否可以产生DMA捕获/比较通道互补输出基本定时器TIM6 / TIM7APB116位向上1~65536可以0无通用定时…

一起学docker系列之四docker的常用命令--系统操作docker命令及镜像命令

目录 前言1 操作 Docker 的命令1.1 启动 Docker1.2 停止 Docker1.3 重启 Docker1.4 查看 Docker 状态1.5 查看 Docker 所有命令的信息1.6 查看某个命令的帮助信息 2 操作镜像的命令2.1 查看所有镜像2.2 搜索某个镜像2.3 下载某个镜像2.4 查看镜像所占空间2.5 删除镜像2.6 强制删…

基于猕猴感觉运动皮层Spike信号的运动解码分析不同运动参数对解码的影响

公开数据集中文版详细描述参考前文&#xff1a;https://editor.csdn.net/md/?not_checkout1&spm1011.2124.3001.6192神经元Spike信号分析参考前文&#xff1a;https://blog.csdn.net/qq_43811536/article/details/134359566?spm1001.2014.3001.5501神经元运动调制分析参考…

2D槽道流

之前看槽道流时&#xff0c;一直无法在二维槽道流里计算出湍流状态&#xff0c;后来了解到二维槽道流需要额外添加随机扰动&#xff0c;但是这个体积力的植入方式一直不知道。而且看稳定性分析中的OS方程的推导&#xff0c;也是基于2d的NS方程&#xff0c;至今还是很疑惑这个问…

Struts2 数据校验之四兄弟

现在是科技的时代&#xff0c;大多数人都在网上购物了&#xff0c; 我们都碰到过相同的问题&#xff0c;各大网站弄的那些各种各样的注册页面&#xff0c;相信大家都深有体会。 有了这验证就很好的保证了我们的信息的准确性和安全性。 接下来我给大家讲解一下用struts2怎么实…

H5ke11--3介绍本地,会话存储

代码顺序: 1.设置input,捕获input如果有多个用属性选择符例如 input[typefile]点击事件.向我们的本地存储设置键值对 2.在点击事件外面设置本地存储表示初始化的值.点击上面的事件才能修改我们想修改的值 会话(session)浏览a数据可以写到本地硬盘,关闭页面数据就没了 本地(…

2023年【P气瓶充装】报名考试及P气瓶充装复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年P气瓶充装报名考试为正在备考P气瓶充装操作证的学员准备的理论考试专题&#xff0c;每个月更新的P气瓶充装复审考试祝您顺利通过P气瓶充装考试。 1、【多选题】充装过程中出现充气头漏气的主要原因为&#xff1…

Java Web——JavaScript运算符与流程语句

1. 运算符 1.1. 算数运算符 数字是用来计算的&#xff0c;比如&#xff1a;乘法 * 、除法 / 、加法 、减法 - 等等&#xff0c;所以经常和算术运算符一起。 算术运算符&#xff1a;也叫数学运算符&#xff0c;主要包括加、减、乘、除、取余&#xff08;求模&#xff09;等 …

git拉取普通idea Java项目module没有build的问题

在不断完成一个项目的时候&#xff0c;会有不断新加的module&#xff0c;我们用git拉取时会发生没有识别新module的情况。 解决方法是右键项目名称&#xff0c;然后点击Open Module Settings 接下来&#xff0c;点击Module&#xff0c;加号&#xff0c;新建Module的名字就是在g…

高效文件管理:一键批量修改文件名,并统一转换为大写扩展名

在日常生活和工作中&#xff0c;文件处理成为了一项必不可少的任务。无论是个人还是企业&#xff0c;都需要管理大量的文件&#xff0c;包括图片、文档、音频和视频等。这些文件的名字可能千奇百怪&#xff0c;格式各不相同&#xff0c;而且往往需要按照一定的规则进行修改或整…

MFC 对话框

目录 一、对话款基本认识 二、对话框项目创建 三、控件操作 四、对话框创建和显示 模态对话框 非模态对话框 五、动态创建按钮 六、访问控件 控件添加控制变量 访问对话框 操作对话框 SendMessage() 七、对话框伸缩功能实现 八、对话框小项目-逃跑按钮 九、小项…

十一、统一网关GateWay(搭建网关、过滤器、跨越解决)

目录 一、网关技术的实现 在SpringCloud中网关的实现包括两种: 作用&#xff1a; 二、搭建网关服务 1、新建模块&#xff0c;并添加依赖 2、新建Gateway包&#xff0c;并编写启动类 3、编写yml文件 4、启动服务&#xff0c;并在网页内测试 5、步骤 三、路由断言工厂 …

Vue3 shallowRef 和 shallowReactive

一、shallowRef 使用shallowRef之前需要进行引入&#xff1a; import { shallowRef } from vue; 使用方法和ref 的使用方法一致&#xff0c;以下是二者的区别&#xff1a; 1. 如果ref 和 shallowRef 都传入的是普通数据类型的数据&#xff0c;那么他们的效果是一样的&#x…

OpenGL 的学习之路-4(变换)

三大变换&#xff1a;平移、缩放、旋转&#xff08;通过这三种变换&#xff0c;可以将图像移动到任意位置&#xff09; 其实&#xff0c;这背后对应的数学在 闫令琪 图形学课程 中有过一些了解&#xff0c;所以&#xff0c;理解起来也不觉得很困难。看程序吧。 1.画三角形&am…

【C++】类和对象(6)--运算符重载

目录 一 概念 二 运算符重载的实现 三 关于时间的所有运算符重载 四 默认赋值运算符 五 const取地址操作符重载 一 概念 C为了增强代码的可读性引入了运算符重载&#xff0c;运算符重载是具有特殊函数名的函数&#xff0c;也具有其返回值类型&#xff0c;函数名字以及参数…

【使用vscode在线web搭建开发环境--code-server搭建】

官方版本下载 https://github.com/coder/code-server/releases?q4.0.0&expandedtrue使用大于版本3.8.0,因为旧版本有插件市场不能访问的情况版本太高需要更新环境依赖 拉取安装包 []# wget "https://github.com/coder/code-server/releases/download/v4.0.0/code-…

[游戏开发][Untiy]跨平台可视化Log系统

工具介绍 今天介绍的主角是LogViewer 工具运行时长这个样子&#xff0c;Unity的Log日志都会在这里显示 如何安装 在Unity商店搜索Log&#xff0c;排名第一的就是它 也可以去Github官网下载源码&#xff1a; Unity-Logs-Viewerhttps://github.com/aliessmael/Unity-Logs-Vie…