当前位置: 首页 > news >正文

命名管道通信和共享内存通信

前言

        进程通信是指在进程间传输数据(交换信息)。 进程通信根据交换信息量的多少和效率的高低,分为低级通信(只能传递状态和整数值)和高级通信(提高信号通信的效率,传递大量数据,减轻程序编制的复杂度)。其中高级进程通信分为三种方式:共享内存模式、消息传递模式、共享文件模式。

        共享内存通讯是指在计算机的内存中开辟一块空间。使这个空间能够被两个进程看见,于是形成通讯。因为实在内存中操作,所以操作系统(OS)不需要和磁盘交换数据,于是效率会更高。

一、命名管道通讯

1. 简介

        命名管道就是共享文件模式,也是本篇博客的中心。命名管道的通讯在于建立通讯用的文件,然后两个进程分别以读和写的方式打开文件。于是乎就能让一个进程读到另一个进程写的东西,从而形成通信。

        有命名管道通信就会有匿名管道通信,但是匿名管道通信只能用于有血缘关系的进程。为了让服务器和客户端进行交互,我们需要建立命名通信管道。于是命名管道文件诞生了。

2. 实现原理

        实现命名管道的通信的主要使用函数包括mkfifo、write、read、open。

2.1. mkfifo

        这是我们第一次学习mkfifo函数,下面让我来介绍一下它的功能和使用方法。

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

        mkfifo用来建立管道文件,它的形式如上所示。其中pathname表示的是文件所在的地址,如果只填写文件名称那么就会在当前文件夹创建文件。mode表示文件的操作权限,会改变文件是否能够读写,一般我们会将其设置为0666。

        如果创建管道文件成功那么会返回0,如果出现错误会返回-1(错误码会被存储起来到errno中)。

        需要注意的是该函数只能用来建立管道文件,不建议用这个函数建立其他性质的文件。

2.2. 其他文件操作函数

        对于其他文件操作函数。open函数用来打开文件、write函数用于client进程向管道内写入数据、read用于server进程读取需要处理的内容。这些函数的使用方法可以参考之前的博客,链接如下:

访问文件和文件重定向_访问代码本地文件,重定向-CSDN博客文章浏览阅读1k次,点赞20次,收藏12次。主讲linux系统中打开文件的接口和对程序的输入输出重定向。_访问代码本地文件,重定向 https://blog.csdn.net/2302_81342533/article/details/145203057

2.3. 实现逻辑

2.3.1. 共同部分

        首先我们能够建立一个类用来封装我们所需要的功能:创建管道文件,删除管道文件、从管道文件中读取数据、向管道文件中写入数据等操作。在描述中,需要文件的地址和名称。建立文件的方式默认为0666,一次最多读取到的字节数默认为1023个。

#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define FIFO_FILE "fifo"
#define EXIT_ERROR(error, error_mode)               \
        do{                                         \
            std::cerr << error;                     \
            exit(error_mode);                       \
        }while(0)


class MyFifo
{
public:
    // 初始化管道名称
    MyFifo(const std::string&& name = FIFO_FILE)
        :_filename(name)
    {
        _path = "./" + name;
        _fifoname = "fifoname=" + _filename;
    }

    // 展示管道文件名称
    bool SayFifoName()
    {
        std::cout << _fifoname << std::endl;
        return true;
    }

    // 创建对应管道文件
    bool Create()
    {
        umask(0);
        int n = mkfifo(_path.c_str(), 0666);
        if(n != 0)
        {
            EXIT_ERROR("mkdir_fifo_error", 1);
        }

        return true;
    }

    // 以读得到方式打开管道文件,并读取数据
    int Read()
    {
        int readline = 0;
        // 打开管道文件
        int fd = open(_path.c_str(), O_RDONLY);
        if(fd < 0)
        {
            EXIT_ERROR("open_fifo_error", 2);
        }

        // 读取数据
        char buffer[1024];
        while(true)
        {
            int n = read(fd, buffer, sizeof(buffer) - 1);
            if(n > 0)
            {
                buffer[n] = 0;
                readline++;
                std::cout << "client say#" << buffer << std::endl;
            }
            else if(n < 0)
            {
                EXIT_ERROR("read_fifo_error", 3);
            }
            else
            {
                std::cout << "read is over\n";
                break;
            }
        }

        close(fd);
        return readline;
    }

    // 向管道内写入数据
    int Write()
    {
        int write_line = 0;
        // 打开文件
        int fd = open(_path.c_str(), O_WRONLY);
        if(fd < 0)
        {
            EXIT_ERROR("open_fifo_error\n", 4);
        }

        // 写入数据
        while(true)
        {
            std::cout << "Pleause Enter#";
            std::string message;
            getline(std::cin, message, '\n');
            if(message == "close write")
            {
                std::cout << message << std::endl;
                break;
            }

            int n = write(fd, message.c_str(), message.size());
            if(n < 0)
            {
                EXIT_ERROR("write to fifo error\n", 5);
            }
            else if(n != message.size())
            {
                std::cerr << "write to fifo had lose byte\n";
            }
            else
            {
                write_line++;
            }

        }

        close(fd);

        return write_line;
    }
    
    bool Remake()
    {
        int n = unlink(_filename.c_str());
        if(n == 0)
        {
            std::cout << "remove fifo success\n";
            return true;
        }
        else
        {
            std::cout << "remove fifo failed\n";
            return false;
        }
        
    }

    ~MyFifo()
    {}

private:
    std::string _path;          // 管道文件地址
    std::string _filename;      // 文件名称
    std::string _fifoname;      // 管道名称
};
2.3.2. server(服务器部分)

        服务器部分用来接收管道信息。

#include "comm.hpp"

int main()
{
    MyFifo fifo;
    // 创建管道
    fifo.Create();
    // 读取管道中的数据
    fifo.Read();
    // 删除管道文件
    fifo.Remake();
    return 0;
}
2.3.3. client(客户端部分)

        客户端部分用来发送信息。

#include "comm.hpp"

int main()
{
    MyFifo write_fifo;
    // 向管道中写入数据
    write_fifo.Write();
    return 0;
}

3. 实际使用

        因为有管道的存在,所以我们server端能够得到client端传来的数据。但我们现在没有对应的任务能够给server执行。所以我们就打印出来client传给server的数据。比如:

        在原有基础上我增加了一些功能,例如用unlink删除管道文件和用“close write”关闭写端。通过增加这样的功能我们也能使客户端执行更负责的操作。但是现在的交流仍然是单向的,如果是双向的话也许我们就需要两个管道才行。

二、 共享内存通信

1. 简介

        共享内存通信,就是指在内存中的两个进程都能够看到同一块区域,于是两个进程就能够进行交流了。这通常被用于两个没有血缘关系的进程,而且因为都是在内存中操作所以效率很高。缺点就是为了实现内存共享,需要约定一个方法使进程都能够找到同一块内存。只要能找到了共享内存就实现了。

2. 实现原理

        在实现通信之前,我们需要将内存共享。那么我们将其分为两步,一步是创建,另一步是分享。创建内存需要用到两个函数ftok和shmget。接下来将介绍他们的形式和用法:

2.1. shmget

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

        函数的作用是在内存中开辟size大小的内存,这就相当于玩游戏时创建房间一样。key代表房间的密码,size表示房间的大小(单位byte),shmflg表示创建房间的方法。其中shmflg有两种常用的命令——IPC_CREAT 、IPC_EXCL,前者表示创建,后者表示唯一。如果需要使用IPC_EXCL就需要和IPC_CREAT一起。

        返回值如果>0则表示创建成功。如果为-1则表示创建失败,失败内容会被记录在errno中。

2.2. ftok

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

        这个函数就是用于构建shmget中的key中默认的方法。pathname表示地址名,proj_id表示打开文件的文件方式。

        如果成功将会返回>0。反之就会返回-1,错误码会被存到errno中。可以使用perror打印。

2.3. 实现逻辑

        实现逻辑和命名通讯部分的实现逻辑相似,构建服务端和客户端,描述共享内存。将方法表示为创建内存和链接内存。

        实现代码暂无。

作者结语

        本节的内容就先到这里,下一节将会是进程池的实现。学习完进程池之后,也能将这一节的两个通讯方式进行池化。

相关文章:

  • 【开源】低代码 C++程序框架,Linux多线程程序
  • 欧拉动力学方程的推导(持续更新)
  • 第十四届蓝桥杯:DFS之飞机降落
  • Java 大视界 -- Java 大数据中的时间序列数据异常检测算法对比与实践(103)
  • Joycon-Robotics库的安装报错解决记录
  • Hadoop简介
  • 【朝夕教育】《鸿蒙原生应用开发从零基础到多实战》003-TypeScript 中的类
  • 转化率(漏斗分析)——mysql计算过程
  • 【实战 ES】实战 Elasticsearch:快速上手与深度实践-1.3.1单节点安装(Docker与手动部署)
  • DDD该怎么去落地实现(4)多对多关系
  • PyTorch的.pt文件详解
  • 进程间通信(中)
  • 计算机科学技术领域的内卷现状与应对措施分析
  • 【软考-架构】备战2025软考
  • L2-005 集合相似度
  • 【EI/IEEE/Scopus检索】智能电网、AI与算法领域国际会议重磅来袭!学术探索的征程从未停歇!
  • Ubuntu20.04安装Redis
  • Scrapy:_RequestBodyProducer 类详解
  • 散列加载描述文件
  • centos7rpm升级openssh至9.8
  • 国家统计局:4月社会消费品零售总额同比增长5.1%
  • 以军在加沙北部和南部展开大规模地面行动
  • 发射后失联,印度地球观测卫星发射任务宣告失败
  • 解读|战国子弹库帛书漂泊海外79年今归国,追索仍将继续
  • 3月中国减持189亿美元美债、持仓规模降至第三,英国升至第二
  • 受贿1.29亿余元,黑龙江省原副省长王一新被判无期