目录
前言
一.管道
0.什么是管道
1).管道的概念
2).管道的本质
3).管道指令: "|"
1.匿名管道
1).如何创建匿名管道
2).如何使用匿名管道进行通信
3).匿名管道的特点总结
2.命名管道
0).指令级的命名管道的通信
1).如何在编程时创建命名管道
2).如何在编程中使用命名管道在进程A与B之间相互通信
3.总结(匿名管道vs命名管道)
1.管道的共性:
2).管道的异性:
二.基于System V标准的共享内存
1.System V标准共享内存的通信原理
2.基于System V标准的共享内存通信的性质
3.System V标准共享内存的系统调用接口
1).常用接口
2).key VS shmid
4.代码实现System V共享内存进程通信
1).纯共享内存版本(没有访问控制)
2).手动添加访问控制版本
5.通过指令操作ipc资源
6.补充: 关于共享内存的删除
7.补充: 关于共享内存与管道的读取的一些区别
前言
为什么需要进程间通信
为了进程之间能够互相协同工作, 从而更高效的完成任务, 就需要进程间通信这样的技术
进程间通信的技术背景
进程是具有独立性的, 其独立性体现在: 每个进程都有属于自己的虚拟地址空间并且通过页表与物理内存映射起来, 所以进程与进程之间是互不干扰的
进程间通信的成本高, 因为想要通信就要打破这种独立性
进程间通信的本质
进程间通信的本质实际上就是让不同的进程可以看到同一块内存
几种进程间通信的技术
1.Linux原生支持 - 管道文件 - 适用于不同进程的单机通信
匿名管道
命名管道
2.System V - 适用于不同进程的单机通信
共享内存
消息队列
信号量
3.Posix - 适用于多线程/多进程间的网络通信
一.管道
0.什么是管道
1).管道的概念
管道有出口, 有入口, 管道中传输的都是数据
管道是单向通信, 是一种特殊的半双工通信方式
半双工: 一个进程不是在读, 就是在写, 不可能同时读写
例如: 老师给学生讲课(就是一种半双工), 老师在讲时, 学生只可以听, 学生在提问时, 老师也只可以听
对于匿名管道而言, 一个管道, 有一个出口, 有一个入口
对于命名管道而言, 一个管道, 可以有多个出口, 可以有多个入口
2).管道的本质
管道本质上就是一个文件, 这个管道文件属于"内存文件", 其实就是内核中的一块缓冲区
"内存文件": 管道文件与普通文件不同的是, 管道文件属于"内存文件", 不向硬盘中刷新, 其实就是一个特殊的struct file内核数据结构, 数据的传输在内核级缓冲区中直接完成, 从而大大提高通信效率
结论:
Linux为何天然就支持单机的进程通信呢? 本质就是Linux的一切皆文件理念, 从而拥有了一种特殊的"内存文件"管道文件, 通过文件进行进程间的通信以及数据的传输
3).管道指令: "|"
每个指令实际上都是一个进程, 进程A | 进程B, 本质上|就是管道, 进程A处理后的数据经过管道交给进程B继续处理, 这是一个单向的过程, 进程A与进程B进行了通信
1.匿名管道
1).如何创建匿名管道
创建匿名管道的系统调用: int pipe(int pipefd[2])
参数:
int pipefd[2], 这是一个输出型参数, 传入一个pipefd[2]整数数组
pipefd[0]代表读端(以读方式打开匿名管道并且存入对应的fd)
pipefd[1]代表写端(以写方式打开匿名管道并且存入对应的fd)
返回值:
如果创建成功返回0, 创建失败返回-1, 并且错误码被设置
官方文档:
图解说明:
2).如何使用匿名管道进行通信
通过进程A创建出匿名管道
进程A调用fork创建子进程B
各自关闭一个读端一个写端 ---> 从而达到A - B之间的进程间通信
图解说明:
代码演示:
#include "log.hpp"
// 让父进程写, 子进程读
int main()
{
// 1.创建匿名管道
int pipefd[2] = {0};
int ret = pipe(pipefd);
assert(ret != -1);
(void)ret;
Log("创建匿名管道成功", Debug);
// 2.fork子进程
int id = fork();
if (id == 0)
{
Log("创建子进程成功", Debug);
// 子进程的逻辑 - 关闭子进程的写端
close(pipefd[1]);
while (true)
{
// 接收匿名管道中的数据
char buffer[SIZE];
memset(buffer, 0, SIZE);
ssize_t n = read(pipefd[0], buffer, SIZE - 1);
if (n > 0)
{
printf("子进程已接收数据: %s\n", buffer);
if (strcmp("quit", buffer) == 0)
{
break;
}
}
else if (n == 0)
{
break;
}
}
// 结束
close(pipefd[0]);
exit(25); // 结束子进程
}
else if (id > 0)
{
// 父进程的逻辑 - 关闭父进程的读端
close(pipefd[0]);
while (true)
{
// 向匿名管道中写入数据
string msgBuffer; // 存放写入数据
getline(cin, msgBuffer);
write(pipefd[1], msgBuffer.c_str(), msgBuffer.size());
if (strcmp("quit", msgBuffer.c_str()) == 0)
{
break;
}
}
// 等待并回收子进程
close(pipefd[1]);
int status = 0;
int ret = wait(&status);
if (ret > 0)
{
cout << "-------------------------------\n";
cout << "我是父进程, 我已经成功回收子进程\n";
cout << "子进程pid: " << ret << endl;
cout << "退出码(status次低八位): " << ((status >> 8) & 0xFF) << endl;
cout << "退出信号(status低七位): " << (status & 0x7F) << endl;
cout << "-------------------------------\n";
}
else if (ret == -1)
{
cout << "我是父进程, 我回收子进程失败\n";
}
}
else
{
// 子进程fork失败
perror("fork");
exit(-1);
}
return 0;
}
3).匿名管道的特点总结
1.只有拥有血缘关系的进程间通信才可以使用匿名管道(常用于父子通信)
2.匿名管道具有访问控制, 这是linux在设计匿名管道的时候就已经设计好的
访问控制:
如果写快, 读慢, 当匿名管道被写满就不能再写了, 只能等读走一部分再继续写
如果写慢, 读快, 管道没有数据的时候, 读进程必须等待
如果写关闭, 则读进程读到0个字节, 表示读到了文件结尾
如果读关闭, 则系统自动关闭写进程
3.管道提供的是面向流式的通信服务(面向字节流), 如果想要控制读取内容则需要手动添加"协议"
4.管道属于文件, 文件描述符的生命周期是伴随进程的, 进程结束就随之释放, 管道做为特殊的文件 - 管道文件, 生命周期也是伴随进程的
当读端关闭了, 写端进程被OS发送信号终止, 管道自然也就被释放
当写端关闭了, 读端进程读到文件结束标志, 读取了0个字节, 手动的也就将读端关闭, 管道自然也就被释放
再或者手动的关闭了两端读写, 管道文件自然也就被释放 -- 这就是管道的生命周期是伴随进程的
5.管道是单项通信的, 因为管道被创建好之后只有一端读, 一端写, 所以是单向通信, 这是一种半双工通信的特殊情况
2.命名管道
命名管道的使用方式就和普通文件一样, 底层实际上与匿名管道相同, 也是一块内核级缓冲区, 与匿名管道不同的就是命名管道被标识了, 有对应的inode, 但该inode仅仅只作为标识, 只是为了让其他进程可以找到该命名管道文件, 仅此而已
0).指令级的命名管道的通信
使用指令创建命名管道: mkfifo [管道名]
指令级的进程通信
如果echo向name_pipe命名管道做输出重定向, 如果此时没有输入重定向, 也就是没有进程读取, 那么向管道输出的进程就会一直阻塞直到有进程读取这块命名管道, 当另一个进程cat从named_pipe做输入重定向也就是读取的时候, 管道中的数据就会被读走, 这也就是一个进程向另一个进程通过命名管道通信的过程
1).如何在编程时创建命名管道
系统调用: int mkfifo(const char* pathname, mode_t mode)
参数:
第一个参数const char* pathname: 所创建的命名管道的路径及名称
(注: 最好填写绝对路径, 因为相对路径会根据运行进程路径的不同而改变, 这样如果两个通信进程的运行时路径不统一那么在通信时, 这个pathname也就不确定了)
第二个参数mode_t mode: 创建出来的命名管道的权限
返回值:
如果创建命名管道成功返回0, 创建失败返回-1, 并且errno被设置
2).如何在编程中使用命名管道在进程A与B之间相互通信
本次让server端读, client端写
log.hpp(包含一些头文件/宏定义/日志信息输出函数)
#ifndef LOG_H
#define LOG_H
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<unistd.h>
#include<assert.h>
#include<cstring>
using namespace std;
#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3
string pathName = "/home/zsl/2022_12_29/named_pipe/namedPipe";
#define SIZE 1024
string flags[] = {
"Debug",
"Notice",
"Warning",
"Error"
};
ostream& Log(const string& msg, int flag)
{
cout << msg << ", " << flags[flag] << endl;
return cout;
}
#endif
server端代码
#include "log.hpp"
// 让server读, client写
int main()
{
// 1.创建命名管道
int ret = mkfifo(pathName.c_str(), 0666);
assert(ret != -1);
(void)ret;
Log("创建命名管道成功", Debug);
// 2.server以读的方式打开命名管道
int fd = open(pathName.c_str(), O_RDONLY);
if (fd < 0)
{
perror("open");
exit(-1);
}
Log("Server已经以读形式打开管道文件", Debug);
// 3.进行通信
Log("开始进行通信", Debug);
char buffer[SIZE];
memset(buffer, 0, SIZE);
while (true)
{
ssize_t n = read(fd, buffer, SIZE - 1);
if (n > 0)
{
if (strcmp("quit", buffer) == 0)
{
break;
}
printf("我是server端, 我已经接收到client端发送的数据: %s\n", buffer);
}
else if(n == 0)
{
printf("client(写端)已退出, 我server端(读端)也退出\n");
break;
}
}
// 4.释放读端
close(fd);
Log("读端已释放", Debug);
// 5.删除管道文件
unlink(pathName.c_str());
Log("管道文件已删除", Debug);
return 0;
}
client端代码
#include"log.hpp"
int main()
{
//server端已经创建好命名管道
//做为client端直接使用即可: client做为写端, 以写的形式打开管道
//1.以写形式打开命名管道
int fd = open(pathName.c_str(), O_WRONLY);
if(fd < 0)
{
perror("open");
exit(-2);
}
//2.与server进行通信
string buffer;
while(true)
{
cout << "我是client端, 请输入要传输的数据:";
getline(cin , buffer);
if(buffer == "quit")
{
break;
}
write(fd, buffer.c_str(), buffer.size());
}
//3.关闭写端
close(fd);
return 0;
}
刚开始执行server端(读端)时, 由于写端还没有开始执行所以会在连接管道时阻塞
当启动client(写端时), 读端继续执行, 开始进行进程通信
进程通信的过程 -- 输入quit就退出通信了
3.总结(匿名管道vs命名管道)
1.管道的共性:
1.有访问控制
对于管道而言:
如果写快, 读慢, 当管道被写满就不能再写了, 只能等读走一部分再继续写
如果写慢, 读快, 管道没有数据的时候, 读进程必须等待
如果写关闭, 则读进程读到0个字节, 表示读到了文件结尾
如果读关闭, 则系统自动关闭写进程
单独的, 对于命名管道多增加出来的访问控制:
如果命名管道被创建出来, 并且读端被执行, 写端还未被执行, 读端会阻塞在打开明明管道文件这一步, 直到写端被执行
2.单向通信
特殊的半双工通信方式
3.本质上都是一块内核级缓冲区
进程之间通过管道文件进行通信, 但是不会向磁盘刷新从而大大提高通信效率
4.面向字节流通信
读写时如果没有人为干预, 是以字节流的方式进行的, 也就是直接读写"范围内的一堆数据", 这个"一堆"指的是一堆字节, 没有格式上的控制
5.管道的生命周期伴随进程
管道的生命周期随进程,本质是内核中的缓冲区,命名管道文件只是标识,用于让多个进程找到同一块缓冲区,删除后,之前已经打开管道的进程依然可以通信, 直至最后通信间的进程也结束之后, 管道才会真正释放
6.读写时的原子性问题(补充内容)
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
2).管道的异性:
对于匿名管道:
1.用于有血缘关系的进程之间进行通信, 多用于父子进程之间通信
2.匿名管道只是操作系统给开辟的一块内核级缓冲区, 没有标识, 通过系统调用pipe, 输出型参数int pipefd[2]中的两个整数, 来控制读写端
对于命名管道:
1.不需要血缘关系, 只要能找到命名管道的两个文件, 都可以进行通信
2.命名管道也是操作系统给开辟的一块内核级缓冲区, 拥有管道名称, 有对应的inode编号, 但仅仅是用来让不同的进程通过该管道名找到管道
从而进行通信, 仅此而已, 没有实际的管道文件内容
二.基于System V标准的共享内存
1.System V标准共享内存的通信原理
想让不同进程间达到通信的目的, 就必须要让这不同的进程看到同一块"空间", 即内存
原理:
由操作系统给开辟的一块专门用来进行通信的物理内存空间, 这块空间不属于任何进程, 然后将这块空间通过页表映射挂接到进程的虚拟地址空间的共享区, 通过获得挂接的起始地址就可以操作整个开辟的共享内存, 即可进行通信
操作系统创建好共享内存之后, 需要对其进行管理, 需要先描述, 再组织
故共享内存 = 物理上的共享内存块 + 对应的共享内存的内核数据结构
所以, 创建好共享内存之后都会返回一个整数, 这个整数类似于文件描述符的fd, 即共享内存描述符shmid
操作:
A进程 B进程
1.通过ftok获取一个相同的key值
2.通过key值创建共享内存 2.通过key值获取A创建好的共享内存
3.将共享内存挂接到进程
4.进行进程间通信
5.去掉进程与共享内存的挂机
6.删除共享内存
图示:
2.基于System V标准的共享内存通信的性质
1.没有访问控制
具体表现为:
如果此时共享内存为空, 读端仍一直读取, 只不过每次读到的内容为空
2.是最快的进程间通信的形式
一但由OS开辟出内存并且映射挂接到进程中, 这些进程间数据传输不再涉及到内核, 也就是不在通过执行内核的系统调用来完成传输, 本质上是通过减少了传输数据时不必要的拷贝, 直接由 进程A ---> 进程B
3.System V标准共享内存的系统调用接口
1).常用接口
1.shmget创建共享内存
包含头文件: #include<sys/ipc.h>
#include<sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数:
key: 使用ftok创建的key值
size: 指定创建的共享内存的大小, 一般为4096的倍数, 因为页表1页是4096字节, 如果开4097, 系统会预留两页, 实际使用只有4097, 则大大浪费空间
shmflg:
常用基础选项1.IPC_CREAT
常用基础选项2.IPC_EXCL
并且要带上权限, 例如0666, 与创建文件同理
单独使用IPC_CREAT, 如果创建的共享内存底层已经存在则获取, 如果不存在则创建
单独使用IPC_EXCL, 没有任何意义
IPC_EXCL必须与IPC_CREAT共同使用, 如果创建的共享内存底层不存在, 则创建, 底层存在则报错
此参数的传入方式通过或运算传入n个标记位
例如: IPC_CREAT | IPC_EXCL | 0666
注:一般如果是非创建共享内存的一端在获取共享内存时, shmflg传入0即可
返回值:
一个整数, 类似于文件描述符的fd, 用来操作该块共享内存, 如果失败返回-1
2.ftok获取创建共享内存的key值(钥匙)
包含头文件:#include<sys/types.h>
#include<sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数:
pathname: 路径
proj_id: 整数
返回值:
成功返回key值, 失败返回-1, 并且设置错误码errno
3.挂接共享内存至进程
包含头文件:#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid: 通过shmget创建的共享内存的返回值
shmaddr: 指定挂接地址, 通常传入nullptr即可, 让OS自动选择挂接地址
shmflg: 通常为0即可
返回值:
返回一个void*指针, 这里类似于malloc, 返回值需要根据使用来强制类型转换, 返回的地址是共享内存的起始地址, 如果失败则返回(void*)-1, 并且错误码errno被设置
4.去挂接
包含头文件:#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
参数:
shmaddr: 传入shmat挂接时返回的地址
返回值:
成功返回0, 失败返回-1, 并且设置错误码errno
5.删除共享内存
包含头文件:#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid: 共享内存块描述符
cmd: 标记位, 传入IPC_RMID宏即为删除
buf: 一般传入nullptr
返回值:
有多种情况, 一般成功返回0, 失败返回-1
2).key VS shmid
key:
通过ftok创建出来的key, key是让想要通信的不同进程, 获取到同一个共享内存shmid的钥匙
进一步解释: 进程A想要与进程B通信, 由进程A创建出了共享内存, 那么进程B如何找到这块共享内存呢? 就是通过key来找到的, 因为A创建shm(share memory)时是通过key创建的, 而key存在的目的也在于此, 进程B也用shmget通过传入key来找到对应的共享内存
shmid:
类似于文件系统的文件描述符, 共享内存不止一块, 所以需要被OS管理起来, OS管理的方式: 先描述, 再组织, 所以创建一块共享内存, 也需要创建对应的内核数据结构来将其描述起来, 而shmid就像是找到这块内核数据结构的标识, 一切的想要操作这块共享内存的系统调用接口都需要通过传入shmid来确定具体操作哪一块共享内存
总体来看:
key是对内核的, 是不同进程用来找到同一shm的钥匙
shmid是对用户的, 是通过shmid来让用户操作这块shm, 例如: shmat, shmdt, shmctl
4.代码实现System V共享内存进程通信
注: 如何体现共享内存是最快的进程通信的方式
直接将共享内存看作是buffer, 也就是读写操作直接在共享内存, 即返回的shmaddr地址进行操作即可
1).纯共享内存版本(没有访问控制)
log.hpp
#ifndef LOG_H
#define LOG_H
#include<iostream>
#include<string>
#include<assert.h>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
using namespace std;
#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3
#define SIZE 4096
#define PROJ_ID 5
string pathName = "/home/zsl/2022_12_29/shm_ipc";
string flags[] = {
"Debug",
"Notice",
"Warning",
"Error"
};
ostream& Log(const string& msg, int flag)
{
cout << msg << ", " << flags[flag] << endl;
return cout;
}
#endif
server.cxx
#include "log.hpp"
int main()
{
key_t key = ftok(pathName.c_str(), PROJ_ID);
assert(key != -1);
(void)key;
Log("server端已获取key", Debug);
int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1)
{
perror("shmget");
exit(-1);
}
Log("server端已创建shm", Debug);
char *shmaddr = (char *)shmat(shmid, nullptr, 0);
assert(shmaddr != (void*)-1);
Log("server端已挂接shm", Debug);
Log("server端开始通信", Debug);
while (true)
{
cout << "请输入通信内容: ";
fflush(stdout);
ssize_t n = read(0, shmaddr, SIZE);
if (n > 0)
{
shmaddr[n - 1] = '\0';
if (strcmp("quit", shmaddr) == 0)
{
break;
}
}
}
Log("server端结束通信", Debug);
int ret = shmdt(shmaddr);
assert(ret != -1);
Log("server端去挂接shm", Debug);
ret = shmctl(shmid, IPC_RMID, nullptr);
assert(ret != -1);
Log("server端已删除shm", Debug);
return 0;
}
client.cxx
#include "log.hpp"
int main()
{
key_t key = ftok(pathName.c_str(), PROJ_ID);
assert(key != -1);
(void)key;
Log("client端已获取key", Debug);
int shmid = shmget(key, SIZE, 0);
if (shmid == -1)
{
perror("shmget");
exit(-1);
}
Log("client端已获取shm", Debug);
char *shmaddr = (char *)shmat(shmid, nullptr, 0);
assert(shmaddr != (void*)-1);
Log("client端已挂接shm", Debug);
Log("client端开始通信", Debug);
while (true)
{
printf("client端已接收到通信内容: %s\n", shmaddr);
if (strcmp("quit", shmaddr) == 0)
{
break;
}
sleep(1);
}
Log("client端结束通信", Debug);
int ret = shmdt(shmaddr);
assert(ret != -1);
Log("client端去挂接shm", Debug);
return 0;
}
纯共享内存的通信方式, 缺乏访问控制, 所以就会出现, 不管共享内存内部没有数据, 读端会一直读取, (如果为空就一直读取空的内容, 如果内容没有更新就一直读取旧数据), 这就是缺乏访问控制的表现
2).手动添加访问控制版本
管道自带访问控制, 如果想让共享内存也有访问控制, 来进行"有规则"的读写, 可以通过用管道来加锁的方式进行控制
AccessControl.hpp
#ifndef ACCESS_CONTROL_HPP
#define ACCESS_CONTROL_HPP
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#include "log.hpp"
#define NAMED_PIPE "/home/zsl/2022_12_29/shm_ipc_access_control/named_pipe"
class Init
{
public:
Init()
{
umask(0);
int n = mkfifo(NAMED_PIPE, 0666);
assert(n != -1);
(void)n;
Log("已创建命名管道", Debug);
}
~Init()
{
int n = unlink(NAMED_PIPE);
assert(n != -1);
(void)n;
Log("已删除命名管道", Debug);
}
};
int OpenNamedPipe(string pathname, int flags)
{
int fd = open(pathname.c_str(), flags);
if (fd < 0)
{
perror("open");
exit(-1);
}
return fd;
}
void Wait(int fd)
{
Log("等待中...", Notice);
int temp = 0;
// 从管道中读取, 如果管道中没有有效数据就会堵塞(等待)
ssize_t n = read(fd, &temp, sizeof(int));
assert(n == sizeof(int));
(void)n;
}
void Signal(int fd)
{
// 向管道中写入一个整数,来唤醒Wait
int temp = 1;
ssize_t n = write(fd, &temp, sizeof(temp));
assert(n == sizeof(int));
(void)n;
Log("唤醒中...", Notice);
}
void Close(int fd)
{
close(fd);
}
#endif
log.hpp
#ifndef LOG_H
#define LOG_H
#include<iostream>
#include<string>
#include<assert.h>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
using namespace std;
#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3
#define SIZE 4096
#define PROJ_ID 5
string pathName = "/home/zsl/2022_12_29/shm_ipc";
string flags[] = {
"Debug",
"Notice",
"Warning",
"Error"
};
ostream& Log(const string& msg, int flag)
{
cout << msg << ", " << flags[flag] << endl;
return cout;
}
#endif
server.cxx
#include "log.hpp"
#include "AccessControl.hpp"
int main()
{
Init init;
int fd = OpenNamedPipe(NAMED_PIPE, O_WRONLY);
Log("server端以写端打开管道, 做为唤醒", Debug);
key_t key = ftok(pathName.c_str(), PROJ_ID);
assert(key != -1);
(void)key;
Log("server端已获取key", Debug);
int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1)
{
perror("shmget");
exit(-1);
}
Log("server端已创建shm", Debug);
char *shmaddr = (char *)shmat(shmid, nullptr, 0);
assert(shmaddr != (void*)-1);
Log("server端已挂接shm", Debug);
Log("server端开始通信", Debug);
while (true)
{
cout << "请输入通信内容: ";
fflush(stdout);
ssize_t n = read(0, shmaddr, SIZE);
if (n > 0)
{
shmaddr[n - 1] = '\0';
Signal(fd);//唤醒
if (strcmp("quit", shmaddr) == 0)
{
break;
}
}
}
Log("server端结束通信", Debug);
Close(fd);
int ret = shmdt(shmaddr);
assert(ret != -1);
Log("server端去挂接shm", Debug);
ret = shmctl(shmid, IPC_RMID, nullptr);
assert(ret != -1);
Log("server端已删除shm", Debug);
return 0;
}
client.cxx
#include "log.hpp"
#include "AccessControl.hpp"
int main()
{
int fd = OpenNamedPipe(NAMED_PIPE, O_RDONLY);
Log("server端以读端打开管道, 做为等待", Debug);
key_t key = ftok(pathName.c_str(), PROJ_ID);
assert(key != -1);
(void)key;
Log("client端已获取key", Debug);
int shmid = shmget(key, SIZE, 0);
if (shmid == -1)
{
perror("shmget");
exit(-1);
}
Log("client端已获取shm", Debug);
char *shmaddr = (char *)shmat(shmid, nullptr, 0);
assert(shmaddr != (void*)-1);
Log("client端已挂接shm", Debug);
Log("client端开始通信", Debug);
while (true)
{
Wait(fd);//等待
printf("client端已接收到通信内容: %s\n", shmaddr);
if (strcmp("quit", shmaddr) == 0)
{
break;
}
}
Log("client端结束通信", Debug);
Close(fd);
int ret = shmdt(shmaddr);
assert(ret != -1);
Log("client端去挂接shm", Debug);
return 0;
}
5.通过指令操作ipc资源
三种进程通信资源
如何查看
ipcs -m --- 查看所有共享内存
ipcs -q --- 查看所有消息队列
ipcs -s --- 查看所有信号量
如何删除
ipcrm -m shmid --- 删除指定共享内存
ipcrm -q msqid --- 删除指定消息队列
ipcrm -s semid --- 删除指定信号量
ipcrm -a --- 删除所有进程通信资源
6.补充: 关于共享内存的删除
共享内存的删除操作并非直接删除, 而是拒绝后续映射, 只有在当前映射链接数为0时(nattch为0), 表示没有进程访问了, 才会真正被删除
7.补充: 关于共享内存与管道的读取的一些区别
管道:
类似于一个循环队列, 数据挨着写入, 挨着读取, 读取过的数据就会被标记为无效, 等下次管道写满了就会从头开始写, 覆盖掉无效数据
共享内存:
类似于malloc的操作方式, 所写入/读取数据的内容, 完全通过人为控制(通过起始地址shmaddr去控制), 如果两次都是从同一地址写入, 那么新数据会直接覆盖旧数据