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

目录

示例代码

头文件(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 3const 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_TRUNCclass 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.hqwc.cn/news/191561.html

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

相关文章

龙芯 操作系统选择和安装

龙芯3a5000及之后的cpu底层架构已经从mips64el改为了loongarch64 所以这里分了2种来说明&#xff0c;分别对应3a4000之前的和3a5000之后的 龙芯的系统安装难点在于操作系统的选取和引导 一、烧录工具 制作安装盘使用常规的烧录工具是不行滴&#xff0c;会提示没有\boot\initrd…

网络和Linux网络_3(套接字编程)TCP网络通信代码(多个版本)

目录 1. TCP网络编程 1.1 前期代码 log.hpp tcp_server.cc 1.2 accept和单进程版代码 1.3 多进程版strat代码 1.4 client.cc客户端 1.5 多进程版strat代码改进多线程 1.6 线程池版本 Task.hpp lockGuard.hpp thread.hpp threadPool.hpp 多个回调任务 tcp_client…

c语言-浅谈指针(3)

文章目录 1.字符指针变量常见的字符指针初始化另一种字符指针初始化例&#xff1a; 2.数组指针变量什么是数组指针变量数组指针变量创建数组指针变量初始化例&#xff08;二维数组传参的本质&#xff09; 3.函数指针变量什么是函数指针变量呢&#xff1f;函数指针变量创建函数指…

浅谈C++重载、重写、重定义

C重载、重写、重定义 重载、重写、重定义对比一、重载&#xff08;overload&#xff09;二、重写 / 覆盖&#xff08;override&#xff09;三、重定义 / 隐藏&#xff08;redefining&#xff09; * 为什么在虚函数中不能使用 static 关键字&#xff1f;动态绑定&#xff08;Dyn…

mac系统安装docker desktop

Docker的基本概念 Docker 包括三个基本概念: 镜像&#xff08;Image&#xff09;&#xff1a;相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。比如说nginx,mysql,redis等软件可以做成一个镜像。容器&#…

heatmap | cell cycle genes in Seurat

目的&#xff1a;使用bulk 数据&#xff0c;查看HeLa 双胸苷阻断法 细胞同步化 释放 [0, 3, 4.5, 6, 9, 10.5, 12, 15, 18, 19.5, 21, 22.5, 25.5, 30] 小时后 cell cycle 基因的表达情况。 1.结果 S phase G2M phase S G2M phase 不方便看&#xff0c;横过来看&#xff1a;…

C语言实现冒泡排序(超详细)

排序算法 - 冒泡排序 什么是冒泡排序&#xff1f;冒泡排序有啥用呢&#xff1f;冒泡排序的实现代码讲解冒泡排序的总结 什么是冒泡排序&#xff1f; 冒泡排序是一种简单的排序算法&#xff0c;它重复地遍历要排序的列表&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序…

UE5制作场景时的小技巧和注意事项

UE5制作场景时的小技巧和注意事项 一、场景相关 1.1灯光 1.1.1构建完光照,发现场景都是黑的 可能是所有灯光是静态灯光,把skylight改为动态,如果改完之后还是黑色的,那就在构建一次,就应该没问题了 1.1.2场景中有多个动态光会造成阴影闪烁 需要将skylight变为固定 1…

若依前后端分离版,快速上手

哈喽~大家好&#xff0c;这篇来看看若依前后端分离版&#xff0c;快速上手&#xff08;肝了挺久的&#xff09;。 &#x1f947;个人主页&#xff1a;个人主页​​​​​ &#x1f948; 系列专栏&#xff1a;【Springboot和Vue全栈开发】…

视频会议设备如何安装?

视频会议设备如何安装&#xff1f; 注意&#xff1a; 建议在干燥通风常温环境下使用 使用接地稳压电源 通电次序&#xff1a;先插网线&#xff0c;再插电源&#xff0c;再上电 IP地址的设置 有两种方式访问并设置连通宝服务器&#xff1a;即插即用和网线直连。 即插即用 …

视频剪辑技巧:轻松搞定视频随机合并,一篇文章告知所有秘诀

在视频制作的过程中&#xff0c;视频随机合并是一种创新的剪辑手法&#xff0c;它打破了传统的线性剪辑模式&#xff0c;使得视频剪辑更加灵活和有趣。通过将不同的视频片段随机组合在一起&#xff0c;我们可以创造出独特的视觉效果和情感氛围。这种剪辑方式让观众在观看视频时…

C#开发的OpenRA游戏之属性BodyOrientation(6)

C#开发的OpenRA游戏之属性BodyOrientation(6) 在顶层定义里会发现这个属性: ^SpriteActor: BodyOrientation: QuantizeFacingsFromSequence: RenderSprites: SpriteActor是用来定义角色的基本属性,它的第一个属性就是BodyOrientation,这个属性主要用来描述角色的身体的…