Linux系统基础-进程间通信(5)_模拟实现命名管道和共享内存

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

Linux系统基础-进程间通信(5)_模拟实现命名管道和共享内存

收录于专栏[Linux学习]
本专栏旨在分享学习Linux的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

[1. 命名管道的实现](#1. 命名管道的实现)

[1.1 命名管道类](#1.1 命名管道类)

定义常量与宏

类定义

成员变量

构造函数

私有方法

公共方法

析构函数

[1.2 客户端](#1.2 客户端)

[1.3 服务端](#1.3 服务端)

[1.4 效果展示:](#1.4 效果展示:)

[2. 共享内存的实现](#2. 共享内存的实现)

[2.1 共享内存类](#2.1 共享内存类)

常量定义

成员变量

私有方法

公共方法

[2.2 命名管道类](#2.2 命名管道类)

[2.3 客户端](#2.3 客户端)

[2.4 服务端](#2.4 服务端)

[2.5 效果展示:](#2.5 效果展示:)


1. 命名管道的实现

1.1 命名管道类

这个类 NamePiped封装了命名管道的基本操作,提供了创建、打开、读取和写入的接口

cpp 复制代码
#pragma

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

const std::string comm_path = "./myfifo";
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096


class NamePiped
{
private:
    bool OpenNamePipe(int mode)
    {
        _fd = open(_fifo_path.c_str(), mode);
        if(_fd < 0)
            return false;
        return true;
    }

public:
    NamePiped(const std::string &path, int who)
        : _fifo_path(path), _id(who), _fd(DefaultFd)
    {
        if(_id == Creater)
        {
            int res = mkfifo(_fifo_path.c_str(), 0666);
            if(res != 0)
            {
                perror("mkfifo");
            }
            std::cout << "create create named pipe" << std::endl;
        }
    }
    bool OpenForRead()
    {
        return OpenNamePipe(Read);
    }
    bool OpenForWrite()
    {
        return OpenNamePipe(Write);
    }

    int ReadNamePipe(std::string *out)
    {
        char buffer[BaseSize];
        int n = read(_fd, buffer, sizeof(buffer));
        if(n > 0)
        {
            buffer[n] = 0;
            *out = buffer;
        }
        return n;
    }

    int WriteNamePipe(const std::string &in)
    {
        return write(_fd, in.c_str(), in.size());
    }

    ~NamePiped()
    {
        if(_id == Creater)
        {
            int res = unlink(_fifo_path.c_str());
            if(res != 0)
            {
                perror("unlink");
            }
            std::cout << "creater free named pipe" << std::endl;
        }
        if(_fd != DefaultFd) close(_fd);
    }
private:
    const std::string _fifo_path;
    int _id;
    int _fd;
};

定义常量与宏

const std::string comm_path = "./myfifo";: 定义了一个常量字符串,表示命名管道的路径。

**#define DefaultFd -1:**定义一个默认的文件描述符值,表示未打开状态。

**#define Creater 1:**定义服务端标识,用于区分管道的服务端和客户端

#define User 2: 定义客服端标识,用于标识读取或写入管道的用户。

**#define Read O_RDONLY 和 #define Write O_WRONLY:**分别定义读取和写入的打开模式。

**#define BaseSize 4096:**定义缓冲区的大小,用于读取数据。

类定义

成员变量

**const std::string _fifo_path;:**保存命名管道的路径。

int _id;: 标识是服务端还是用户。

int _fd;: 文件描述符,用于读写操作。

构造函数

**初始化成员变量:**使用初始化列表设置路径、身份和默认文件描述符。

**创建管道:**如果 _id 是 Creater(服务端),则调用 mkfifo 创建命名管道。如果创建失败,使用 perror 输出错误信息。

私有方法

OpenNamePipe(int mode) : 功能: 封装打开命名管道的逻辑。

调用 open 函数尝试打开管道。

如果打开失败,返回 false,成功则返回 true。

公共方法

**OpenForRead()**功能: 调用 OpenNamePipe以只读模式打开管道。

OpenForWrite()功能: 调用OpenNamePipe以只写模式打开管道。

**ReadNamePipe(std::string *out)**功能: 从管道读取数据。

定义一个缓冲区,调用 read 从文件描述符读取数据。

如果读取成功,将数据存入输出参数,并返回读取的字节数。

**WriteNamePipe(const std::string &in)**功能: 向管道写入字符串数据。

析构函数

~NamePiped() 功能: 释放资源和清理工作。

如果 _id 是 Creater,则使用 unlink 删除命名管道。

关闭文件描述符(如果已打开)。

1.2 客户端

cpp 复制代码
#include "namedPipe.hpp"

//write
int main()
{
    NamePiped fifo(comm_path, User);
    if(fifo.OpenForWrite())
    {
        std::cout << "client open named pipe done" << std::endl;
        while(true)
        {
            std::cout << "Please Enter > ";
            std::string message;
            std::getline(std::cin, message);
            fifo.WriteNamePipe(message);
        }
    }
    return 0;
}

1.3 服务端

cpp 复制代码
#include "namedPipe.hpp"
//server read : 管理命名管道的整个生命周期

int main()
{
    NamePiped fifo(comm_path, Creater);
    //对于进程而言, 如果我们打开文件, 但是写还没来, 进程会阻塞在open调用中, 直到对方打开
    //进程同步
    if(fifo.OpenForRead())
    {
        std::cout << "server open named pipe done" << std::endl;
        sleep(3);
        while(true)
        {
            std::string message;
            int n = fifo.ReadNamePipe(&message);
            if(n > 0)
            {
                std::cout << "Client Say > " << message << std::endl;
            }
            else if(n == 0)
            {
                std::cout << "Client quit, Server Too!" << std::endl;
            }
            else
            {
                std::cout << "fifo.ReadNamedPipe Error" << std::endl;
                break;
            }
        }
    }
    return 0;
}

1.4 效果展示:

通过命名管道, 我们完成了客户端与服务器端的通信! (因为命名管道不需要两个进程具有血缘关系)

2. 共享内存的实现

完成上述客户端与服务器端的通信, 除了使用命名管道, 共享内存同样可以!

2.1 共享内存类

cpp 复制代码
#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
 #include <string.h>

const int gCreater = 1;
const int gUser = 2;
const std::string gpathname = "/home/cjb/linux-operating-system-network/linux22/shm";
const int gproj_id = 0x66;
const int gShmSize = 4097; // 4096*n

class Shm
{
private:
    key_t GetCommKey()
    {
        key_t k = ftok(_pathname.c_str(), _proj_id);
        if (k < 0)
        {
            perror("ftok");
        }
        return k;
    }
    int GetShmHelper(key_t key, int size, int flag)
    {
        int shmid = shmget(key, size, flag);
        if (shmid < 0)
        {
            perror("shmget");
        }
        return shmid;
    }

    std::string RoleToString(int who)
    {
        if (who == gCreater)
            return "Creater";
        else if (who == gUser)
            return "gUser";
        else
            return "None";
    }
    void *AttachShm()
    {
        if (_addrshm != nullptr)
            DetachShm(_addrshm);
        void *shmaddr = shmat(_shmid, nullptr, 0);
        if (shmaddr == nullptr)
        {
            perror("shmat");
        }
        std::cout << "who: " << RoleToString(_who) << "attach shm..." << std::endl;
        return shmaddr;
    }

    void DetachShm(void *shmadder)
    {
        if (shmadder == nullptr)
            return;
        shmdt(shmadder);
        std::cout << "who: " << RoleToString(_who) << "detach shm..." << std::endl;
    }

public:
    Shm(const std::string &pathname, int proj_id, int who)
        : _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr)
    {
        _key = GetCommKey();
        if (_who == gCreater)
            GetShmUseCreate();
        else if (_who == gUser)
            GetShmForUse();
        _addrshm = AttachShm();

        std::cout << "shmid: " << _shmid << std::endl;
        std::cout << "_key: " << ToHex(_key) << std::endl;
    }

    ~Shm()
    {
        if (_who == gCreater)
        {
            int res = shmctl(_shmid, IPC_RMID, nullptr);
        }
        std::cout << "shm remove done..." << std::endl;
    }

    std::string ToHex(key_t key)
    {
        char buffer[128];
        snprintf(buffer, sizeof(buffer), "0x%x", key);
        return buffer;
    }

    bool GetShmUseCreate()
    {
        if (_who == gCreater)
        {
            _shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);
            if (_shmid >= 0)
                return true;
            std::cout << "shm create done..." << std::endl;
        }
        return false;
    }

    bool GetShmForUse()
    {
        if (_who == gUser)
        {
            _shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);
            if (_shmid >= 0)
                return true;
            std::cout << "shm get done..." << std::endl;
        }
        return false;
    }

    void Zero()
    {
        if (_addrshm)
        {
            memset(_addrshm, 0, gShmSize);
        }
    }

    void *Addr()
    {
        return _addrshm;
    }

    void DebugShm()
    {
        struct shmid_ds ds;
        int n = shmctl(_shmid, IPC_STAT, &ds);
        if (n < 0)
            return;
        std::cout << "ds.shm_prem.__key : " << ToHex(ds.shm_perm.__key) << std::endl;
        std::cout << "ds.shm_nattch" << ds.shm_nattch << std::endl;
    }

private:
    key_t _key;
    int _shmid;

    std::string _pathname;
    int _proj_id;

    int _who;
    void *_addrshm;
};

常量定义

gCreatergUser:分别表示创建者和用户角色的常量,用于区分不同的操作角色。

gpathname:共享内存文件路径,ftok 使用此路径生成唯一的键。

gproj_id:用于生成唯一键的项目 ID。

gShmSize:共享内存的大小,设置为 4097 字节(必须是 4096 的倍数)。

成员变量

_key:存储生成的共享内存键。

_shmid:共享内存 ID。

_pathname:存储共享内存路径。

_proj_id:存储项目 ID。

_who:当前对象的角色(创建者或用户)。

_addrshm:指向当前附加的共享内存的指针。

私有方法

GetCommKey():

使用 ftok 函数生成共享内存的键。如果生成失败,打印错误信息。

GetShmHelper():

封装对 shmget 的调用,尝试获取共享内存 ID。如果失败,打印错误信息。

RoleToString():

将角色 ID 转换为字符串,方便调试输出。

AttachShm():

尝试附加共享内存。如果 _addrshm 不为空,先调用 DetachShm() 分离它。使用 shmat 函数附加共享内存,并打印调试信息。

DetachShm():

封装对 shmdt 的调用,用于分离共享内存。如果传入的指针为空,直接返回。

公共方法

构造函数:

接受路径、项目 ID 和角色,并初始化相关变量。

调用 GetCommKey() 获取键。

根据角色调用 GetShmUseCreate() 或 GetShmForUse() 来获取共享内存。

调用 AttachShm() 附加共享内存,打印共享内存 ID 和键。

析构函数:

如果角色是创建者,调用 shmctl 删除共享内存(IPC_RMID)。

ToHex():

将共享内存键转换为十六进制字符串,方便输出调试信息。

GetShmUseCreate():

仅在创建者角色时调用,尝试创建共享内存段(IPC_CREAT | IPC_EXCL),成功返回 true。

GetShmForUse():

仅在用户角色时调用,尝试获取共享内存段(IPC_CREAT),成功返回 true。

Zero():

清零共享内存内容,使用 memset。

Addr():

返回当前附加的共享内存地址。

DebugShm():

获取共享内存的状态信息(如键和连接数),使用 shmctl 和 IPC_STAT。

2.2 命名管道类

和上面一样, 不需要改变

cpp 复制代码
#pragma

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

const std::string comm_path = "./myfifo";
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096

// const std::string comm_path = "./myfifo";: 定义了一个常量字符串,表示命名管道的路径。
// #define DefaultFd -1: 定义一个默认的文件描述符值,表示未打开状态。
// #define Creater 1: 定义创建者标识,用于区分管道的创建者和用户。
// #define User 2: 定义用户标识,用于标识读取或写入管道的用户。
// #define Read O_RDONLY 和 #define Write O_WRONLY: 分别定义读取和写入的打开模式。
// #define BaseSize 4096: 定义缓冲区的大小,用于读取数据。

// 这个类 NamePiped 封装了命名管道的基本操作,提供了创建、打开、读取和写入的接口
class NamePiped
{
private:
    bool OpenNamePipe(int mode)
    {
        _fd = open(_fifo_path.c_str(), mode);
        if(_fd < 0)
            return false;
        return true;
    }

public:
    NamePiped(const std::string &path, int who)
        : _fifo_path(path), _id(who), _fd(DefaultFd)
    {
        if(_id == Creater)
        {
            int res = mkfifo(_fifo_path.c_str(), 0666);
            if(res != 0)
            {
                perror("mkfifo");
            }
            std::cout << "create create named pipe" << std::endl;
        }
    }
    bool OpenForRead()
    {
        return OpenNamePipe(Read);
    }
    bool OpenForWrite()
    {
        return OpenNamePipe(Write);
    }

    int ReadNamePipe(std::string *out)
    {
        char buffer[BaseSize];
        int n = read(_fd, buffer, sizeof(buffer));
        if(n > 0)
        {
            buffer[n] = 0;
            *out = buffer;
        }
        return n;
    }

    int WriteNamedPipe(const std::string &in)
    {
        return write(_fd, in.c_str(), in.size());
    }

    ~NamePiped()
    {
        if(_id == Creater)
        {
            int res = unlink(_fifo_path.c_str());
            if(res != 0)
            {
                perror("unlink");
            }
            std::cout << "creater free named pipe" << std::endl;
        }
        if(_fd != DefaultFd) close(_fd);
    }
private:
    const std::string _fifo_path;
    int _id;
    int _fd;
};

2.3 客户端

cpp 复制代码
#include "Shm.hpp"
#include "namedPipe.hpp"

int main()
{
    // 1. 创建共享内存

    Shm shm(gpathname, gproj_id, gUser);
    shm.Zero();
    char *shmaddr = (char *)shm.Addr();
    sleep(3);

    // 2. 打开管道
    NamePiped fifo(comm_path, User);
    fifo.OpenForWrite();

    // 当成string
    char ch = 'A';
    while (ch <= 'Z')
    {
        shmaddr[ch - 'A'] = ch;

        std::string temp = "wakeup";
        std::cout << "add " << ch << " into Shm, " << "wakeup reader" << std::endl;
        fifo.WriteNamedPipe(temp);
        sleep(2);
        ch++;
    }
    return 0;
}

2.4 服务端

cpp 复制代码
#include "Shm.hpp"
#include "namedPipe.hpp"

int main()
{
    // 1. 创建共享内存
    Shm shm(gpathname, gproj_id, gCreater);
    char *shmaddr = (char*)shm.Addr();

    shm.DebugShm();

    sleep(5);
    
    return 0;
}

2.5 效果展示:

相关推荐
mubeibeinv7 分钟前
项目搭建+图片(添加+图片)
java·服务器·前端
dessler12 分钟前
Docker-如何启动docker
运维·docker·云原生·容器·eureka
zhy2956312 分钟前
【DOCKER】基于DOCKER的服务之DUFS
运维·docker·容器·dufs
无为之士17 分钟前
Linux自动备份Mysql数据库
linux·数据库·mysql
秋名山小桃子27 分钟前
Kunlun 2280服务器(ARM)Raid卡磁盘盘符漂移问题解决
运维·服务器
与君共勉1213828 分钟前
Nginx 负载均衡的实现
运维·服务器·nginx·负载均衡
荒古前30 分钟前
龟兔赛跑 PTA
c语言·算法
Colinnian34 分钟前
Codeforces Round 994 (Div. 2)-D题
算法·动态规划
岑梓铭34 分钟前
(CentOs系统虚拟机)Standalone模式下安装部署“基于Python编写”的Spark框架
linux·python·spark·centos