Bootstrap

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

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

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

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

目录

1. 命名管道的实现

1.1 命名管道类

定义常量与宏

类定义

成员变量 

构造函数

私有方法

公共方法

析构函数

1.2 客户端

1.3 服务端

1.4 效果展示: 

2. 共享内存的实现

2.1 共享内存类

常量定义

成员变量

私有方法

公共方法

2.2 命名管道类

2.3 客户端

2.4 服务端

2.5 效果展示:


1. 命名管道的实现

1.1 命名管道类

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

#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 客户端

#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 服务端

#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 共享内存类

#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;
};

常量定义

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

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 命名管道类

和上面一样, 不需要改变

#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 客户端

#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 服务端

#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 效果展示:

;