个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创Linux系统基础-进程间通信(5)_模拟实现命名管道和共享内存
收录于专栏[Linux学习]
本专栏旨在分享学习Linux的一点学习笔记,欢迎大家在评论区交流讨论💌
目录
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;
}