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


    目录

    示例代码

    头文件(comm.hpp)

    log.hpp

    基础版 -- 服务端

    代码

    运行情况

    加入客户端

    代码

    运行情况

    两端进行通信 

    客户端

    代码

    注意点

    服务端

    代码

    两端运行情况

    共享内存特点

    拷贝次数少

    管道的拷贝次数

    共享内存的拷贝次数

    没有访问控制

    管道

    共享内存

    并发问题

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

    代码

    头文件

    服务端

    客户端

    运行情况


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

    示例代码

    头文件(comm.hpp)

    1. #ifndef COMM_H
    2. #define COMM_H
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include "log.hpp"
    14. #include
    15. using namespace std;
    16. #define PATH_NAME "/home/mufeng"
    17. #define PROJ_ID 0x1234
    18. #define SUM_SIZE 4096
    19. #endif

    log.hpp

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

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. #define debug 0
    7. #define notice 1
    8. #define warning 2
    9. #define error 3
    10. const string msg[]{
    11. "debug", "notice", "warning", "error"};
    12. ostream &log(string message, int level)
    13. {
    14. cout << "|" << (unsigned)time(nullptr) << "|" << message << "|" << msg[level] << "|";
    15. return cout;
    16. }

    基础版 -- 服务端

    代码

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

    1. #include"comm.hpp"
    2. int main(){
    3. key_t key = ftok(PATH_NAME, PROJ_ID);
    4. assert(key != -1);
    5. (void)key;
    6. log("key created success", debug) << ", key : " << key << endl;
    7. int shmid = shmget(key, SUM_SIZE, IPC_CREAT|IPC_EXCL|0666);
    8. assert(shmid != -1);
    9. (void)shmid;
    10. log("shm created success", debug) << ", shmid : " << shmid << endl;
    11. sleep(3);
    12. char *addres = (char *)shmat(shmid, nullptr, 0);
    13. log("process link success", debug) << endl;
    14. sleep(3);
    15. int ret = shmdt(addres);
    16. assert(ret != -1);
    17. (void)ret;
    18. log("process unlink success", debug) << endl;
    19. sleep(3);
    20. ret = shmctl(shmid, IPC_RMID, nullptr);
    21. assert(ret != -1);
    22. (void)ret;
    23. log("shm unlink success", debug) << endl;
    24. sleep(3);
    25. return 0;
    26. }

    运行情况

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

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

     

    加入客户端

    代码

    相似的操作

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

    1. int main()
    2. {
    3. key_t key = ftok(PATH_NAME, PROJ_ID);
    4. if (key < 0)
    5. {
    6. log("key created failed", error) << ", client key : " << key << endl;
    7. }
    8. int shmid = shmget(key, SUM_SIZE, 0);
    9. if (shmid < 0)
    10. {
    11. log("shmid created failed", error) << ", client shmid : " << shmid << endl;
    12. }
    13. char *addres = (char *)shmat(shmid, nullptr, 0);
    14. if (addres == nullptr)
    15. {
    16. log("process link failed", error) << endl;
    17. }
    18. sleep(3);
    19. int ret = shmdt(addres);
    20. if (ret < 0)
    21. {
    22. log("process unlink failed", error) << endl;
    23. }
    24. sleep(3);
    25. //这里不需要删除,服务端会将这块内存释放掉
    26. return 0;
    27. }

    运行情况

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

     

    两端进行通信 

    客户端

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

    服务端

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

    1. while (true){
    2. if (strcmp(addres, "quit") == 0)
    3. {
    4. break;
    5. }
    6. cout << addres << endl;
    7. sleep(2);
    8. }

     

    两端运行情况

     

    共享内存特点

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

    拷贝次数少

    管道的拷贝次数

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

       

    共享内存的拷贝次数

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

     

    没有访问控制

    管道

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

    共享内存

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

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

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

    代码
    头文件
    1. // 加入访问控制(通过管道来传递信号,接收到信号才进行读取)
    2. #define FIFO_PATH "./fifo"
    3. #define READ O_RDONLY
    4. #define WRITE O_WRONLY | O_TRUNC
    5. class Init //让管道文件具有类的特性,出作用域自动释放
    6. {
    7. public:
    8. Init()
    9. {
    10. umask(0);
    11. int ret = mkfifo(FIFO_PATH, 0666);
    12. assert(ret == 0);
    13. (void)ret;
    14. log("fifo created success", notice) << endl;
    15. }
    16. ~Init()
    17. {
    18. unlink(FIFO_PATH);
    19. log("fifo removed success", notice) << endl;
    20. }
    21. };
    22. void wait_signal(int fd) //读取指定文件内容作为信号
    23. {
    24. uint32_t signal = 0;
    25. log("waiting ...", notice) << endl;
    26. ssize_t size = read(fd, &signal, sizeof signal);
    27. assert(size == sizeof(uint32_t));
    28. (void)size;
    29. }
    30. void send_signal(int fd) //向指定文件写入signal
    31. {
    32. uint32_t signal = 1;
    33. ssize_t size = write(fd, &signal, sizeof signal);
    34. assert(size == sizeof(uint32_t));
    35. (void)size;
    36. log("being awakened ...", notice) << endl;
    37. }
    38. int open_fifo(string path, int flags) //以指定方式打开创建好的管道文件
    39. {
    40. int fd = open(path.c_str(), flags);
    41. assert(fd >= 0);
    42. return fd;
    43. }
    44. void close_fifo(int fd)
    45. {
    46. close(fd);
    47. }
    服务端
    • 创建管道文件
    • 等待客户端的信号(也就是等待管道文件中出现内容时)
    • 被唤醒后打印addres中的内容
    1. //通信
    2. // 添加访问控制
    3. Init init; // 创建管道文件
    4. int fd = open_fifo(FIFO_PATH, READ);
    5. while (true)
    6. {
    7. wait_signal(fd); // 等待唤醒
    8. if (strcmp(addres, "quit") == 0)
    9. {
    10. break;
    11. }
    12. cout << addres << endl;
    13. }
    14. close_fifo(fd); // 通信结束
    客户端
    • 打开创建好的管道文件
    • 读取键盘输入内容,存入addres中
    • 成功输入时,向服务端发送信号(也就是向管道写入数据)
    1. // 添加访问控制
    2. int fd = open_fifo(FIFO_PATH, WRITE);
    3. while (true)
    4. {
    5. ssize_t size = read(0, addres, SUM_SIZE - 1);
    6. if (size > 0)
    7. {
    8. addres[size - 1] = 0; //处理回车符
    9. send_signal(fd);
    10. if (strcmp(addres, "quit") == 0)
    11. {
    12. break;
    13. }
    14. }
    15. }

     

    运行情况

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

    当客户端接入后:

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

    退出:

  • 相关阅读:
    SpringBoot启动流程梳理-自定义实现@SpringBootApplication注解
    windows 定制 terminal 上手实践
    DSPE-PEG-Aldehyde DSPE-PEG-CHO 磷脂-聚乙二醇-醛基疏水18碳磷脂
    BEVFusion:A Simple and Robust LiDAR-Camera Fusion Framework 论文笔记
    重庆自考免考申请条件介绍
    卫语句-前端应用
    基于FPGA的 图像边沿检测
    算法每日一题(反转单链表)C语言版
    API接口请求电商数据平台参数获取淘宝商品描述示例
    C语言力扣第46题之全排列。回溯法
  • 原文地址:https://blog.csdn.net/m0_74206992/article/details/134469750