目录
命名管道
mkfifo函数
unlink函数
命名管道类
服务端
客户端
共享内存
shmget函数
ftok函数
key和shmid的区别
snprintf函数
ipcs指令
ipcrm指令
shmctl函数
shmat函数
void*做返回值
创建共享内存空间
服务端
客户端
命名管道
基本概念:用于实现进程间通信的有名管道,让不同的进程看到同一份资源
注意事项:
1、创建时需要提供文件的路径
2、命名管道在文件系统中作为一个特殊的文件而存在,但命名管道中的内容却存放在内存中,向命名管道中写入的数据不会刷新到磁盘中
mkfifo函数
函数原型:int mkfifo(const char *pathname, mode_t mode);
包含头文件: <sys/types.h> 和 <sys/stat.h>
参数:指定文件路径,创建文件时的权限
返回值:成功创建返回0,创建失败返回-1
功能:在文件系统中创建一个命名管道文件,看起来是文件但其实是管道
注意事项:管道文件在磁盘上有文件名和路径,但不占用磁盘空间来存储数据
unlink函数
函数原型:int unlink(const char *pathname);
包含头文件:<unistd.h>
参数:要删除的文件的路径
返回值:成功删除返回0,删除失败返回-1
功能:删除指定的文件
注意事项:若文件的硬链接计数为零,则释放该文件占用的磁盘空间(但是命名管道文件不占用磁盘)
命名管道类
功能:负责管道的创建和销毁
/*
* @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @Date: 2024-05-12 13:57:58
* @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @LastEditTime: 2024-05-14 20:39:56
* @FilePath: /2024.5.8/home/ubuntu/2024.5.12/namePipe.hpp
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#pragma once
#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"; // 提供文件的路径(相当于在当前目录下生成一个叫myfifo的命名管道),string字符串在使用时需要转换为c类型的字符串
#define DefaultFd -1 // 将-1用于标识管道的默认fd(有进程打开管道时再分配新的fd)
#define Creater 1 // 将1用于标识管道的申请者
#define User 2 // 将2用于标识管道的使用者
#define Read O_RDONLY // 将Read用于标识只读方式
#define Write O_WRONLY // 将Write用于标识只写方式
#define BaseSize 4096 // 将4096作为读取内容的最大字节数
class NamePiped
{
private:
bool OpenNamedPipe(int mode) // 依据对管道的操作方式
{
_fd = open(_fifo_path.c_str(), mode); // 每个进程打开管道时都会获得OS分配给该进程得文件描述符
if (_fd < 0)
return false; // 打开文件失败返回false
return true; // 打开文件成功返回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 << "creater create named pipe" << std::endl;
}
}
// 以读方式打开管道
bool OpenForRead()
{
return OpenNamedPipe(Read);
}
// 以写方式打开管道
bool OpenForWrite()
{
return OpenNamedPipe(Write);
}
// 读管道的方式
int ReadNamedPipe(std::string *out) // 输出型参数
{
char buffer[BaseSize]; // 一次能读取到的最大内容
int n = read(_fd, buffer, sizeof(buffer));//从管道中读取
if (n > 0)
{
buffer[n] = 0;
*out = buffer; //*out指向读取到的内容
}
return n;
}
// 写管道的方式
int WriteNamedPipe(const std::string &in)
{
return write(_fd, in.c_str(), in.size()); // 依据wfd读取
}
~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) // 关闭写端fd
close(_fd);
}
private:
const std::string _fifo_path; // 提供的文件路径
int _id; // 标识使用者的身份信息
int _fd; // 标识文件描述符
};
服务端
功能:申请并创建管道,以及最后管道的销毁,读取管道中的内容(读端)
/*
* @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @Date: 2024-05-12 13:59:38
* @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @LastEditTime: 2024-05-12 18:42:19
* @FilePath: /2024.5.8/home/ubuntu/2024.5.12/sever.cpp
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#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.ReadNamedPipe(&message);
if (n > 0)
{
std::cout << "Client Say> " << message << std::endl;
}
else if (n == 0)
{
std::cout << "Client quit, Server Too!" << std::endl;
break;
}
else
{
std::cout << "fifo.ReadNamedPipe Error" << std::endl;
break;
}
}
}
return 0;
}
客户端
功能:向管道中写入消息,向管道中写入内容(写端)
/*
* @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @Date: 2024-05-12 13:57:27
* @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @LastEditTime: 2024-05-12 18:52:30
* @FilePath: /2024.5.8/home/ubuntu/2024.5.12/client.cc
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#include "namedPipe.hpp"
// write
int main()
{
NamePiped fifo(comm_path, User); // 管道的使用者
if (fifo.OpenForWrite()) // 调用以写方式打开管道函数
{
std::cout << "client open namd pipe done" << std::endl; // 客户端持续输入
while (true)
{
std::cout << "Please Enter> ";
std::string message;
std::getline(std::cin, message); // 将获取的命令行字符串作为message写入管道
fifo.WriteNamedPipe(message);
}
}
return 0;
}
- 输入型参数:只读,不修改
- 输出型参数:由函数初始化或修改,用于返回数据
- 输入输出型参数:由调用者初始化,由函数修改,并返回修改后的结果
注意事项:
1、有了fifo才能客户端使用与管道相关的方法,即便有的方法根本用不上(构造函数主要是为了服务端创建管道的)
2、read函数会一直等待管道中有数据输入,除非将管道的模式设置为非阻塞等待,但是一般不会这样设置
共享内存
- 共享内存空间是可以存在多份的,每一对儿通信的进程都有一个共享内存空间
- ①和②都是由OS进行的,用户想要进行这些操作需要使用OS提供的系统调用接口
- 共享内存 = 内存空间(数据) + 共享内存的属性
- 共享内存不随着进程的结束而自动释放,它会一直存在直到系统重启,需要使用指令和系统调用接口手动销毁内存(共享内存生命周期随内核,文件的生命周期随进程)
shmget函数
函数原型:int shmget(key_t key, size_t size, int shmflg);
包含头文件:<sys/ipc.h> 和 <sys/shm.h>
参数:“协商”后的共享内存空间的标识符,要创建的共享内存空间的大小,操作方式
返回值:创建成功返回标识符
功能:创建新的共享内存段
注意事项:shmflg取值的不同会使得shmget函数产生不同的效果:
- IPC_CREAT:不存在就新建。存在就,则什么都不干
- IPC_EXCL:单独使用没有意义需要和IPC_CREAT配合使用
- IPC_CREAT | IPC_EXCL:使用两个标识符,如果共享内存存在则返回错误,如果不存在则新建一个共享内存段
- 使用
IPC_CREAT
时,还可以与权限位进行按位或运算来指定共享内存段的访问
ftok函数
函数原型:key_t ftok(const char *pathname, int proj_id);
包含头文件:<sys/ipc.h> 和 <sys/types.h>
参数:用于生成 key 的路径名,项目标识符
返回值:返回生成的 key
功能:根据给定的路径名和项目标识符生成一个 key,生成一个足够随机的用于标识共享内存空间的标识符
注意事项:该函数内部进行的操作是依据路径和项目标识符生成一个key_t类型的随机数,后续两个进程间通信时会使用相同的两个参数生成两个相同的key,从而可以找到对应的共享内存块
key和shmid的区别
key是一个由自定义的(文件路径 + 随机值)经过ftok函数中的算法生成的一个随机值,服务端和客户端共用一个key,服务端将该key交给OS提供的系统调用接口shmget就可以创建一块共享内存空间并获取OS对于该块共享内存空间的标识符shmid,服务端也将该key交给shmget可以获得与服务端相同的shmid(相当于服务端会去创建一个共享内存空间用于二者间的通信,但是客户端会找不到,于是二者提前约定好一个key,服务端说我会用这个key交给shmget会创建一个共享内存空间并得到它的shmid,你找的时候就用这个key去找,找的方式就是也将key交给shmget同时你要保证你是寻找而不是创建,将咱俩约定好的key交给shmget后它会告诉你我创建的共享内存空间的shmid是什么,之后咱俩就拿着这个shmid交流了),最后客户端和服务端就要利用该shmid访问该块共享内存空间(服务端和客户端有一对儿一样的钥匙,服务端用钥匙去找前台shmget开了一个只能用该钥匙打开的房间号为shmid的房间,客户端来找服务端的时候也要去找前台shmget说之前有一个人拿着跟我一样的钥匙开了个房间你告诉我这个房间号吧,然后shmget就拿着你的钥匙去找之前的开房信息,发现你这把钥匙对应了某个房间号,然后后就将该房间号shmid,告诉客户端)
snprintf函数
函数原型:int snprintf(char *str, size_t size, const char *format, ...);
包含头文件:<stdio.h>
参数:待写入的目标字符串,要写入的长度(可以包括\0),格式化字符串,可变参数列表
返回值:写入到字符串中的字符数
功能:向目标字符串中写入指定长度的字符
注意事项:是sprintf的升级版
ipcs指令
功能:查看共享内存段信息的命令
常见用法:
1、ipcs -m查看共享内存段信息
- 键值 (key):用于唯一标识共享内存段的键值
- 权限 (perms):显示共享内存段的权限,包括所有者、组和其他用户的权限
- 拥有者 (owner):拥有共享内存段的用户
- 组 (group):拥有共享内存段的用户组
- 大小 (bytes):共享内存段的大小(以字节为单位)
- 连接的进程 (nattch):当前连接到共享内存段的进程数量
- 最后连接时间 (attime):最后一个进程连接到共享内存段的时间
ipcrm指令
功能:删除共享内存段
常见用法:ipcrm -m 共享内存的shmid
shmctl函数
函数原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
包含头文件: <sys/ipc.h> 和 <sys/shm.h>
参数:共享内存段的标识符,指定要执行的操作,指向共享内存空间属性结构体的指针
返回值:成功指向返回
功能:对共享内存段进行相关操作,比如:
- 获取共享内存段的信息(cmd = IPC_STAT)
- 设置共享内存段的权限(cmd = IPC_SET)
- 删除共享内存段(cmd = IPC_RMID)
struct shmid_ds ds; int n = shmctl(_shmid, IPC_STAT, &ds);
shmat函数
函数原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
包含头文件:<sys/types.h> 和 <sys/shm.h>
参数: 共享内存段的标识符,指定共享内存连接到进程地址空间的地址,IPC选项
返回值:连接成功返回指向共享内存空间(虚拟地址)首字节的指针,失败返回-1
功能:将共享内存空间连接到调用进程的地址空间,以便该进程可以访问和操作共享内存中的数据
注意事项:shmaddr的取值通常为
NULL
,表示让OS自动选择合适的地址,shmflg通常为0int shmdt(const void* shmaddr)为取消连接函数,与shmat函数一样的头文件,shmaddr指向要分离的共享内存段的地址
void*做返回值
使用原因:void*是一种通用指针类型,可以指向任何类型的数据,当函数需要返回一个指针但该指针指向的数据类型可能不确定时(比如malloc函数只负责申请空间并返回一个指向该内存空间的void*类型的指针,调用者要根据需要将该void*指针转换为新类型的指针,从而告诉编译器我们在这块内存中存放的是什么类型的数据),通常会将返回类型声明为void*(内存中的某个位置本身并没有数据类型,数据类型是用来解释内存中的内容的方式。当我们将一个指针转换为特定类型的指针时,我们告诉编译器如何解释这块内存中的内容)
注意事项:在使用void* 类型的返回值时,通常需要将其转换为实际的数据类型,以便正确的访问和操作数据
创建共享内存空间
#ifndef _SHM_HPP_ // 头文件只能包含一次
#define _SHM_HPP_
#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
// 创建使用者的标识
const int gCreater = 1;
const int gUser = 2;
// 自定义全局全局参数用于让两个通信的进程生成同样的key
const std::string gpathname = "/home/ubuntu/2024.5.13";
const int gproj_id = 0x66;
// 自定义共享内存空间的大小,一般为4096 * n
const int gShmSize = 4096;
class Shm
{
// 不希望将这些接口暴露给用户,用户只需要使用即可
private:
// 获取key
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 "User";
else
return "None";
}
// 将共享内存空间挂接到进程地址空间,调用该函数相当于调用malloc函数
void *AttachShm()
{
if (_addrshm != nullptr)
DatechShm(_addrshm);
std::cout
<< "进行挂接" << std::endl;
void *shmaddr = shmat(_shmid, nullptr, 0);
if (shmaddr == nullptr) // 挂接失败
{
perror("shmat");
}
std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;
return shmaddr;
}
// 解除挂接
void DatechShm(void *shmaddr)
{
std::cout << "解除挂接" << std::endl;
if (shmaddr == nullptr)
return;
shmdt(shmaddr);
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) // 如果是创建者就调用专门为创建者准备的函数GetShmForCreater
GetShmForCreater();
else if (_who == gUser) // 如果是使用者就调用专门为使用者准备的函数GetShmForUser
GetShmForUser();
std::cout << "shmid: " << _shmid << std::endl;
std::cout << "_key: " << ToHex(_key) << std::endl;
if (_who == gUser)
{
std::cout << "shm get done..." << std::endl;
}
// 不论是谁只要获取到一个共享内存空间的shmid,就将该共享内存空间挂接到进程地址空间上
_addrshm = AttachShm();
}
// 析构函数:删除共享内存空间
~Shm()
{
if (_who == gCreater)
{
int res = shmctl(_shmid, IPC_RMID, nullptr);
std::cout << "shm remove done..." << std::endl;
}
}
// 将key转换为16进制,便于后续的查看
std::string ToHex(key_t key)
{
char buffer[128];
snprintf(buffer, sizeof(buffer), "0x%x", _key);
return buffer;
}
// 服务端创建共享内存空间
bool GetShmForCreater()
{
_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666); // 如果没有就创建,如果有了就返回错误,即没有返回错误那么就是创建了一个新的共享内存空间
if (_shmid >= 0) // 0666表示所有者、所属组和其他用户对共享内存空间都有读写权限,但没有执行权限
{
std::cout << "shm creat done..." << std::endl;
return true; // 创建成功返回true
}
std::cout << "shm creat fail..." << std::endl;
return false;
}
// 客户端使用共享内存
bool GetShmForUser()
{
_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666); // 使用者根据与创造者一样得_key获得依据创造者创造的共享内存空间得shmid,仅有IPC_CREAT是因为此时服务端已经创建好了共享内存空间此时用户端只需要找到它对应的共享内存空间的shmind即可
if (_shmid >= 0)
{
return true; // 获取成功返回true
}
std::cout << "shm get fail..." << std::endl;
return false;
}
// 获取进程空间的地址
void *Addr()
{
return _addrshm;
}
// 清空共享内存空间
void Zero()
{
if (_addrshm)
{
memset(_addrshm, 0, gShmSize);
}
}
// 获取共享内存空间属性结构体中的内容
void DebugShm()
{
struct shmid_ds ds;
int n = shmctl(_shmid, IPC_STAT, &ds); //
if (n < 0)
return;
std::cout << "ds.shm_perm.__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;
};
#endif
服务端
/*
* @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @Date: 2024-05-12 23:03:25
* @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @LastEditTime: 2024-05-14 20:02:49
* @FilePath: /2024.5.8/home/ubuntu/2024.5.13/server.cc
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#include "shm.hpp"
#include "namedPipe.hpp"
int main()
{
// 1. 创建共享内存
Shm shm(gpathname, gproj_id, gCreater);
char *shmaddr = (char *)shm.Addr();
shm.DebugShm();
// 2. 创建管道
NamePiped fifo(comm_path, Creater);
fifo.OpenForRead(); // 以读方式打开管道
while (true)
{
std::string temp;
fifo.ReadNamedPipe(&temp); // 服务端读取管道
std::cout << "shm memory content: " << shmaddr << std::endl;//打印此时共享内存空间中的内容(客户端已经向里面写入了),shmaddr是一个字符指针,打印它就会将它所指向字符串
}
sleep(5);
return 0;
}
客户端
/*
* @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @Date: 2024-05-12 23:03:16
* @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @LastEditTime: 2024-05-14 20:01:41
* @FilePath: /2024.5.8/home/ubuntu/2024.5.13/client.cc
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#include "shm.hpp"
#include "namedPipe.hpp"
int main()
{
// 1. 创建共享内存
Shm shm(gpathname, gproj_id, gUser);
shm.Zero();
char *shmaddr = (char *)shm.Addr(); // shmaddr指向一段内存空间
sleep(3);
// 2. 打开管道(利用管道保护共享内存空间)
NamePiped fifo(comm_path, User);//有了fifo才能使用与管道相关的方法,即便有的方法根本用不上
fifo.OpenForWrite(); // 以写方式打开管道
// 当成string
char ch = 'A';
while (ch <= 'Z')
{
shmaddr[ch - 'A'] = ch; // shmadrr是一个指针,而数组名相当于一个指针,这里我们将hmaddr视为一个数组,循环向数组中写入即循环向共享内存空间中写入,起始时ch-'A' = 0即shmaddr[0] = 'A'
std::string temp = "wakeup";//定义一个提醒字符串
std::cout << "add " << ch << " into Shm, " << "wakeup reader" << std::endl;//客户端打印自己此时向共享内存空间中所写字母
fifo.WriteNamedPipe(temp);//向管道中写入提醒字符串,提醒服务端该读取了
sleep(2);
ch++;
}
return 0;
}
注意事项:使用管道是因为共享内存空间不会提供对自己本身的保护机制,这就会导致数据不一致问题(客户端还没写完,服务端就疯狂的读取),使用管道 + read函数的方式,当客户端向共享内存空间中写入数据后会向管道中发送一份可以读取提醒,否则服务端就会一直因read读取不到管道中的内容而阻塞,当服务端读取到客户端向管道中写入的提醒时就会开始读取(当然读取什么并不重要我们要的是能让他阻塞等待客户端写完),服务端不再阻塞后就会打印此时共享内存空间中的内容(相当于读取了)
~over~