赞
踩
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!
提示:以下是本篇文章正文内容,下面案例可供参考
- man mkfifo
- // 查看创建一个命名管道文件的指令
- mkfifo myfifo
- // 创建一个myfifo为名的管道文件(文件的第一个属性为p)
int mkfido(const char *pathname,mode_t mode);
命名管道可以从命令行上删除,命令行方法是使用下面这个命令:
- man 2 unlink
- // 查看删除管道文件的指令
- unlink myfile
- // 删除指定的myfile特殊的管道文件
命名管道也可以从程序里删除,相关函数有:
- int unlink(const char* pathname);
- // 删除指定目录下的文件
Mikefile
- .PHONY:all
- all : client server
-
- client : client.cc
- g++ - o $@ $ ^ -std = c++11
- server:server.cc
- g++ - o $@ $ ^ -std = c++11
- .PHONY:clean
- clean :
- rm - rf client server
namedPipe.hpp // 命名管道
- #include <iostream>
- #include <cstdio>
- #include <cerrno>
- #include <string>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
-
- // 把管道文件的路径(公共路径)放在当前路径下,管道文件的名字叫做myfifo
- const std::string comm_path = "./myfifo";
-
- // 将文件描述符默认定义为-1
- #define DefaultFd -1
-
- // 定义管道文件的创建者和使用者
- #define Creater 1
- #define User 2
-
- // 以只读和只写的方式打开文件
- #define Read O_RDONLY
- #define Write O_WRONLY
-
- // 定义一个通信的基本大小
- #define BaseSize 4096
-
- class NamePiped
- {
- private:
- // 打开文件(方式)
- bool OpenNamedPipe(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)
- {
- // 初始化期间,将管道构建好了
- // mkfifo创建命名管道的接口
- 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);
- }
- // const &(输入型参数),比如:const std::string &XXX
- // *(输出型参数) 比如: std::string *
- // &(输入输出型参数) 比如: std::string &
- // 假如双方通信的内容是字符串
- int ReadNamedPipe(std::string* out)
- {
- char buffer[BaseSize];// 定义一个缓冲区
- // 将对应文件描述符中的数据读到buffer数组中
- int n = read(_fd, buffer, sizeof(buffer));
- if (n > 0)
- {
- buffer[n] = 0;
- *out = buffer;// 将buffer数组中的内容带出去了
- }
- return n;
- }
- int WriteNamedPipe(const std::string& in)
- {
- // c_str:C风格的字符串
- return write(_fd, in.c_str(), in.size());
- }
-
- // 删除管道文件
- ~NamePiped()
- {
- if (_id == Creater)
- {
- // unlink删除管道文件的接口
- 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;// 使用管道文件当前进程的pid
- int _fd;// 打开文件的方式,文件描述符
- };
server.cc // 服务端
- #include "namedPipe.hpp"
-
- // server进程read的方式打开管道文件,而且也要管理命名管道的整个生命周期
- int main()
- {
- // 利用管道文件的类创建一个变量,并以创建者的身份调用构造函数创建公共的管道文件(comm_path)
- 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);// 把对应的数据读到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;
- }
client.cc // 客户端
- #include "namedPipe.hpp"
-
- // write
- int main()
- {
- // 以使用者的身份,只使用公共管道的文件(comm_path)
- 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);// 按行从cin中获取消息,将消息输入到message中
- fifo.WriteNamedPipe(message);
- }
- }
-
- return 0;
- }
第一种通信方案:本地通信方案的代码:System V IPC
System V:标准 IPC:进程间通信
描述共享内存的结构体Struct Shm中一定要有标识共享内存唯一性的字段!
- man shmget
- // 查看申请一个System V版本的共享内存
int shmget(key_t key,size_t size,int shmflg);
参数3:
key是什么?参考参数1的解释。
为什么要有key?为了让两个进程能通过key值找同一份资源(共享内存)。
key值是用户形成的,用户如何形成的呢?使用ftok()系统调用接口形成。
int ftok(const char* pathname,int proj id);
b进程怎么知道A进程通过系统调用shmget()接口让OS创建共享内存呢?
shmget()函数的返回值是共享内存的标识符,ftok()函数的返回值也是共享内存的标识符(key值),着两者有何区别呢?
原先的进程创建一个文件,进程结束之后,文件会被自动释放;
可是对于共享内存来说:shmget()函数的参数3使用IPC_CREAT | IPC_EXCL这两个标记位:会看到第一次进程使用shmget()函数创建共享内存,会有对应的key值和共享内存;进程结束后,第二次进程再次使用shmget()函数创建共享内存,会发现创建共享内存失败,返回-1,因为原先的key值还存在,已经被占用了,所以得出的结论:共享内存不随着进程的结束而自动释放。
共享内存不是由进程创建的,而是由OS让进程使用shmget()系统调用接口创建的。
我们要释放共享内存,不然共享内存会一直存在,直到系统重启。
手动释放(指令或者其它的系统调用)
- int shmctl(int shmid,int cmd,struct shmid_ds *buf);
- // 移除成功返回0,否则失败返回-1
- // 参数1:OS给用户的共享内存的标识符
- // 参数2:将cmd替换成IPC_RMID
- // 参数3:OS提供的一个内核级的数据结构(共享内存),来获取共享内存的属性;也可以设置共享内存的结构体,设置成nullptr就可
IPC_RMID:是一个命令,大写的形式,也是一个宏,用来进行标志共享内存当前是被删除的。
IPC_STAT:使用这个命令,使OS在调用shmctl()系统调用接口时,会把内核当中共享内存所有的属性拷贝到参数3的结构体中。
- // 将System V标准的共享内存挂接到对应的进程地址空间当中
- void *shmat(int shmid,const void *shmaddr,int shmflg);
- // 参数1:用户级的共享内存标识符;
- // 参数2:对应的共享内存挂接到哪个地址上,今天不考虑,设为nullptr;
- // 参数3:设置共享内存的访问权限,默认是读写的,设置为0;
- // 成功返回:地址空间中,共享内存的起始地址;失败:返回nullptr
- int shmdt(const void *shmaddr);
- // 参数:shmat()接口的返回值(进程地址空间的起始地址)
dt:detach(分离) shm:共享内存 at:attach(挂接、关联的意思)
shm.hpp // 共享内存的所有接口
- // 共享内存的所有接口 client:客户 server:服务器
- // 防止头文件包含
- #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;
- const std::string gpathname = "/home/whb/code/111/code/lesson22/4.shm";// 设路径为当前的路径
- const int gproj_id = 0x66;// 随便设一个项目id
- const int gShmSize = 4097; // 定义一个共享内存的大小,建议共享内存的大小为4096的整数倍
-
- class Shm
- {
- private:
- // 获取公共的key值
- key_t GetCommKey()
- {
- // string类型的对象_pathname中的C格式的字符串
- 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);
- // shmat():挂接到进程地址空间的接口
- 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* shmaddr)
- {
- if (shmaddr == nullptr)
- return;
- // shmdt()接口
- 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)
- 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)
- {
- // shmctl():删除指定的共享内存
- 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()进行格式化输出,将输出的内容放在一个缓冲区里,将key格式化成0x16进制
- // 因为使用ipcs -m指令查找到的共享内存的key值是0x16进制的
- 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;
- }
- // 将共享内存清0
- void Zero()
- {
- if (_addrshm)
- {
- // 将_addrshm共享内存全部初始化为0
- memset(_addrshm, 0, gShmSize);
- }
- }
- void* Addr()
- {
- return _addrshm;
- }
- // 获取共享内存的属性
- void DebugShm()
- {
- struct shmid_ds ds;
- // IPC_STAT:使用这个命令,使OS在调用shmctl()系统调用接口时,会把内核当中共享内存所有的属性拷贝到参数3的结构体中
- 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;// OS给用户使用的共享内存的标识符
- std::string _pathname;// 为了ftok()函数的参数1随机设置一个共享内存的路径变量
- int _proj_id; // 为了ftok()函数的参数2随机设置一个共享内存的id变量
- int _who;// 进程的身份
- void* _addrshm;// 共享内存挂接到进程地址空间的起始地址的变量
- };
- #endif
server.cc // 服务端(读端)
- #include "Shm.hpp"
- #include "namedPipe.hpp"
-
- int main()
- {
- // 1. 创建共享内存
- // 使用Shm的类型创建一个shm的对象,调用构造函数
- 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;
- // }
- sleep(5);
- return 0;
- }
client.cc // 客户端(写端)
- #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当作一个数组,每隔两秒向共享内存中写入一个字符
- 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;
- }
共享内存不提供对共享内存的任何保护机制,比如:写端向共享内存中写入hello world数据,读端是不会等待写端的,会直接读取数据,写端可能只写了hello数据,还来不及写入world数据,就被读端读取了,读取的数据需要解析,但是数据不完整,会解析有误。这种问题叫做数据不一致。
所以我们可以提供管道的机制,管道提供了同步机制,让客户端和服务端两个进程除了创建共享内存之外,也把管道建立好;服务器读数据之前,都得先读管道,管道里没数据,就等待;当客户端写完数据后,让管道通知服务器,使服务器从管道中读取数据。
至于管道文件里写入和读取什么样的数据,这些不重要;重要的是我们让管道文件的同步机制来变相的使两个进程在共享内存中写入和读取数据的过程也建立同步机制。
我们在上面管道文件的 namedPipe.hpp 和 Mikefile 文件就看拿下来,这里就不拿了。
我们在访问共享内存的时候,没有用任何的系统调用。
共享内存是所有进程间通信(IPC)中,速度最快的,因为,共享内存大大减少了数据的拷贝次数!
原理:OS能够开辟结构性的内存空间,OS会在OS内部给我们申请一个消息队列(msg_queue),刚开始队列为空,然后我们有两个进程A和B,进程A可以使用消息队列的系统调用接口,来向消息队列中放入节点,数据块会自动的来连入消息队列;因为进程间通信要让两个进程看到同样的资源,所以进程B也可以往消息队列中放入节点;它们都往公共的队列里放节点时,那么此时B可以拿A放的节点,A也可以拿B放的节点,这种方式叫消息队列。
因为进程A和B都在消息队列中放入很多的节点,为了区分这些节点,所以要求消息队列中发出去的节点都必须叫做有类型。
获取消息队列
控制消息队列
同步:让多个执行流之间,在执行时,具有一定的顺序性。
互斥:一个公共资源时,我正在访问,你就不能访问,任何一个时刻,只允许我一个人访问这部分公共资源。
假如共享内存中有1000字节,可以把1000字节看成50字节一块一块的小的内存块,我们不想整体使用1000个字节的共享内存,那么A进程访问第一块50个字节的区域,B进程访问第5块50个字节大小的区域,C进程访问第n个50个字节大小的区域....,可以允许多个执行流同时访问共享内存中的局部性的资源。
- if (gcount > 0)
- {
- gcount--;
- }
- else
- {
- wait;// 等待的过程,就是++的时候(联想电影院的票数)
- }
信号量的本质:就是一个计数器,比如:gcount = 25.
申请信号量 -- ->就是P操作;访问公共资源(共享内存);释放信号量 ++ ->就是V操作。
申请和释放信号量,还要保证安全性,叫做PV操作。
可不可以用一个进程级别的全局变量来充当对公共资源的保护呢?不能。
原因:对于父子进程来说,会发生写实拷贝;对于两个毫不相干的进程来说,一个进程定义一个全局变量,另一个进程是看不到的;所以要用信号量来对公共资源的保护。
信号量也是一个公共资源。
允许用户一次申请多个信号量,多个信号量叫信号量集(用数组来维护的)。
- int semget(key_t key,int nsems,int semflg);// 获取信号量
- // 参数2:申请一个信号量集中有几个信号量
允许用户一次申请多个信号量和一个信号量的值为n是两个概念
好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。
不积硅步,无以至千里;不积小流,无以成江海。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。