进程间通信的目的
数据传输: 一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另个进程的所有陷入和异常,并能够及时知道它的状态改变。
管道通信
管道是os中最早的进程通信方式
从一个进程连接搭配另一个进程的数据流称为一个管道
匿名管道
进程通信的的本质就是让不同的进程看到同一块“内存”(特定的数据结构),这一块内存不属于任何一个进程,更应该是共享出来的。那么管道的技术就是给输入和输出进程各有一个口,管道的内容都是单向传输的,传输的就是数据,这种方式在通信的专业名字叫半双工。
那么匿名管道是怎么实现的?
1.分别以读写的方式打开同一个问题
2.fork()创建子进程
3.双方进程各自关闭自己不需要的文件描述符,比如子进程读数据就关闭写文件描述符,父进程写数据就关闭读的文件描述符
以子进程读数据就关闭写文件描述符,父进程写数据就关闭读的文件描述符举例
父进程write数据,child去read数据,他们用同一个地址空间去访问,这种方法可以用一个缓冲区去当中间地址空间。
pipe
man 2 pipe
先来查看一下管道的接口函数,直接调用一个大小为2的数组就能完成创建。
这里父进程在sleep3秒后在buff写入字符串数据,子进程使用read阻塞式等待数据传来,父进程如果不传数据子进程就一直等待。
#include<iostream>
#include<vector>
#include<unistd.h>
#include<assert.h>
#include <cstdlib>
#include <ctime>
#include <sys/types.h>
#include <sys/wait.h>
#include<string.h>
#include<string>
using namespace std;
int main(){
//1.先创建大小为2的数组,用来关闭输入或者输出
int pipefd[2]={0};
int n=pipe(pipefd);
cout<<n<<endl;
assert(n==0);
(void)n;
//2.建立缓冲区buff
char buff[64]={};
pid_t id=fork();
if(id==0){
//4.子进程读取
while(true){
cout<<"wait father"<<endl;
close(pipefd[1]);
read(pipefd[0],buff,sizeof(buff));
cout<<buff<<endl;
cout<<"this is child:"<<buff<<endl;
exit(1);
}
}else{
//5.父进程写入
close(pipefd[0]);
const char* str="hello word hahahaha";
strcpy(buff,str);
cout<<buff<<endl;
cout<<"this is father"<<endl;
sleep(3);
write(pipefd[1],buff,strlen(buff));
}
pid_t waitid=waitpid(id,NULL,0);
cout<<waitid<<endl;
return 0;
}
进程池模拟
先拆开看最后再来看整体代码
1.先加载任务和定义一个处理任务的slots,其中slots采用pair键值对插入
其中load的执行流程是这样的,分别对desc和callback插入任务和执行任务进行
2.假设有五个进程,我们要让他随机取任务进行操作,那么每个进程都会去取随机的操作,然后将该操作通过pipe传输给子进程。
父进程和子进程的操作
调用waitCommand函数,如果没有数据,那么就一直等待。
随机选择任务进行处理
关闭父进程写的窗口和回收子进程
Task.hpp
#pragma once
#include<iostream>
#include<unordered_map>
#include<string>
#include<functional>
#include<vector>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
typedef std::function<void()> func;
vector<func> callbacks;
unordered_map<int ,string> desc;
void readMySQL(){
std::cout<<"sub process["<<getpid()<<"]执行访问数据库任务\n"<<std::endl;
}
void execuleUrl(){
std::cout<<"sub process["<<getpid()<<"]执行url解析\n"<<std::endl;
}
void cal(){
std::cout<<"sub process["<<getpid()<<"]执行加密任务\n"<<std::endl;
}
void save(){
std::cout<<"sub process["<<getpid()<<"]执行数据库持久化任务\n"<<std::endl;
}
void load(){
desc.insert({callbacks.size(),"readMySQL:读取数据库"});
callbacks.push_back(readMySQL);
desc.insert({callbacks.size(),"execuleUrl:进行url解析"});
callbacks.push_back(execuleUrl);
desc.insert({callbacks.size(),"cal:进行加密计算"});
callbacks.push_back(cal);
desc.insert({callbacks.size(),"save进行数据的文件保存"});
callbacks.push_back(save);
}
void showHandler()
{
for(const auto &iter : desc )
{
std::cout << iter.first << "\t" << iter.second << std::endl;
}
}
int handlerSize(){
return callbacks.size();
}
process.cc
#include<iostream>
#include<vector>
#include<unistd.h>
#include<assert.h>
#include <cstdlib>
#include <ctime>
#include <sys/types.h>
#include <sys/wait.h>
#include"Task.hpp"
#define PROCESS_NUM 5
using namespace std;
int waitCommand(int waitFd,bool &quit){
uint32_t command=0;
ssize_t s=read(waitFd,&command,sizeof(command));
if(s==0){
quit=true;
return -1;
}
assert(s==sizeof(uint32_t));
return command;
}
void sendAndWakeup(pid_t who,int fd,uint32_t command){
write(fd,&command,sizeof(command));
cout<<"main process:call process"<<who<<"execute"<<desc[command]<<"through"<<fd<<endl;
}
int main(){
load();//加载任务
vector<pair<pid_t,int>> slots;//插入处理任务
for(int i=0;i<PROCESS_NUM;i++){
int pipefd[2]={0};//创建管道
int n=pipe(pipefd);//0代表读1代表写
assert(n==0);
(void)n;
pid_t id=fork();
assert(id!=-1);
if(id==0){
close(pipefd[1]);
while(true){
bool quit=false;
int command=waitCommand(pipefd[0],quit);
if(quit)break;
if(command>=0&&command<handlerSize()){
callbacks[command]();
}else{
cout<<"非法commond:"<<command<<endl;
}
}
exit(1);
}
//父亲写入关闭读端
close(pipefd[0]);
slots.push_back(pair<pid_t,int>(id,pipefd[1]));
}
showHandler();
srand((unsigned long)time(nullptr)^getpid()^23333L);
int n=5;
while(true){
int command=rand()%handlerSize();
int choice=rand()%slots.size();
sendAndWakeup(slots[choice].first,slots[choice].second,command);
sleep(1);
}
for(const auto &slot:slots){
close(slot.second);
}
for(const auto &slot:slots){
waitpid(slot.first,nullptr,0);
}
}
Makefile
mypipe:mypipe.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f mypipe
匿名管道总结
1.管道是用来进行具有血缘关系的进程进性进程间通信
2.管道具有通过让进程间协同,提供了访问控制!
3.管道提供的是面向流式的通信服务 -- 面向字节流 --协议
4.管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的!4
5.管道是单向通信的 ,就是半双工通信的一种特殊情况
a.写快,读慢,写满不能在写了
b.写慢,读快,管道没有数据的时候,读必须等待
c.写关,读到‘\0’位置,标识读到了文件结尾
d.读关,写继续写,写满的话OS终止写进程
命名管道
先让不同的的进程看到同一份资源,双方进程就可以通过管道文件的路径,看到同一份资源,该路径在系统中一定是唯一的!管道文件可以被打开,但是并不会将数据刷新到磁盘上。
mkfifo
根据路径创建一个管道,mode代表权限的意思,成功就返回0,失败就返回-1
man 3 mkfifo
1就代表管道文件
设置一个路径,能让server和client都能找到的
建立日志文件
创建客户端用来发消息
创建服务端用来接收消息
命名管道代码
client.cc
#include"common.hpp"
int main(){
int fd=open(ipcpath.c_str(),O_WRONLY);
if(fd<0){
perror("open");
exit(1);
}
string buff;
log("登陆服务器成功",0);
cout<<"please cin string"<<endl;
while(true){
log("消息",1);
std::getline(std::cin,buff);
write(fd,buff.c_str(),sizeof buff);
if(strcmp(buff.c_str(),"quit")==0){
break;
}
}
close(fd);
return 0;
}
server.cc
#include"common.hpp"
int main(){
int pipe=mkfifo(ipcpath.c_str(),0666);
assert(pipe=-1);
(void)pipe;
log("创建管道成功",0);
int fd=open(ipcpath.c_str(),O_RDONLY);
if(fd<0){
perror("open");
exit(1);
}
log("打开文件成功,等待读取",0);
while(true){
char buff[64];
read(fd,buff,sizeof(buff));
cout<<buff<<endl;
if(strcmp(buff,"quit")==0){
break;
}
memset(buff,'\0',sizeof buff);
}
log("退出服务器",2);
unlink(ipcpath.c_str());
log("删除管道",3);
}
common.hpp
#pragma once
#include<iostream>
#include<cstring>
#include<cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include<cstdlib>
#include<assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>
#include <ctime>
#include"Log.hpp"
using namespace std;
std::string ipcpath="./fifo.ipc";
Makefile
.PHONY:all
all: server client
server:Server.cc
g++ -o $@ $^ -std=c++11
client:Client.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f server client
Log.hpp
#include"common.hpp"
#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3
const std::string msg[] = {
"Debug",
"Notice",
"Warning",
"Error"
};
std::ostream &log(std::string message,int level){
std::cout<<"|"<<(unsigned)time(nullptr)<<"|"<<msg[level]<<"|"<<message<<std::endl;
return std::cout;
}
system V共享内存
共性内存的建立
共享内存提供者,是操作系统操作系统要不要管理共享内存?
当然要->先描述在组织->重新理解 共享内存 = 共享内存块 +对应的共享内存的内核数据结构
创建共享内存接口shmget
共享内存的用户层标识符,类似曾经的fd
shmflg
IPC_CREAT and IPC_EXCL
如果创建共享内存,如果底层已经存在,获取之,并且返回,如果不存在,创建之,并返回
IPC_CREAT and IPC_EXCL
如果底层不存在,创建之,并返回如果底层存在,出错返回。那么就意外着返回成功一定是全新的shm。
key
通过key,数据是几,不重要只要能够在系统唯一即可 -> server && client ->使用同一个key ->只要key值相同,就是看到了同一个共享内存!
ftok函数
使用同样的算法规则形成唯一值!
key t ftok(Const char *pathname, int pro id);
那么共享内存的好处就是不用经过系统调用,直接可以访问!
双方进程如果要通信,直接进行内存级的读和写即可!
ftok
查看根据路径和值生成一个共享key,这个key用于双方通信
shmget
用于创建共享内存,如果双方的key一样的话,那么就可以通信
shmat
用法和malloc差不多,在堆上开辟出一块空间。
shmdt
断开共享内存连接,成功返回0,失败返回-1
shmctl
删除共享内存,成功返回0,失败返回-1
ipcs和ipcrm
ipcs可以查看有几个共享内存,而ipcrm可以用来删除共享内存
共享内存实现
memserver.cc
#include"common.hpp"
int main(){
key_t k=ftok(PATH_NAME,PROJ_ID);
if(k<0){
log("create key failed",Error)<<"server key"<<k<<endl;
exit(1);
}
log("create key success",Debug)<<"server key"<<k<<endl;
int shmid=shmget(k,SHM_SIZE,IPC_CREAT|IPC_EXCL|0666);
if(shmid<0){
log("create shm failed",Error)<<"server shmid"<<shmid<<endl;
exit(2);
}
log("create shm success",Debug)<<"server shmid"<<shmid<<endl;
char * shmaddress=(char *)shmat(shmid,nullptr,0);
if(shmaddress==nullptr){
log("create shmaddress failed",Error)<<"server shmid"<<shmid<<endl;
exit(3);
}
log("create shmaddress success",Debug)<<"server shmid"<<shmid<<endl;
// int fd=OpenFIFO(PATH_NAME,O_RDONLY);
int fd=OpenFIFO(FIFO_NAME,O_RDONLY);
while(true){
Wait(fd);
printf("%s\n",shmaddress);
if(strcmp(shmaddress,"quit")==0)break;
}
int n=shmdt(shmaddress);
if(n<0){
log("deatch shm failed",Error)<<"server shmid"<<n<<endl;
exit(4);
}
log("deatch shm success",Debug)<<"server shmid"<<n<<endl;
int ctl=shmctl(shmid,IPC_RMID,nullptr);
if(ctl<0){
log("delete shm failed",Error)<<"server shmid"<<ctl<<endl;
}
log("delete shm success",Debug)<<"server shmid"<<ctl<<endl;
return 0;
}
shmclient.cc
#include"common.hpp"
int main(){
key_t k=ftok(PATH_NAME,PROJ_ID);
if(k<0){
log("create key failed",Error)<<"client key"<<k<<endl;
exit(1);
}
log("create key success",Debug)<<"client key"<<k<<endl;
int shmid=shmget(k,SHM_SIZE,0);
if(shmid<0){
log("create shm failed",Error)<<"client shmid"<<shmid<<endl;
exit(2);
}
log("create shm success",Debug)<<"client shmid"<<shmid<<endl;
sleep(2);
char* shmaddress=(char*)shmat(shmid,nullptr,0);
if(shmaddress==nullptr){
log("create shmaddress failed",Error)<<"client shmid"<<shmid<<endl;
exit(3);
}
log("create shmaddress success",Debug)<<"client shmid"<<shmid<<endl;
int fd=OpenFIFO(FIFO_NAME,O_WRONLY);
while(true){
ssize_t s=read(0,shmaddress,SHM_SIZE-1);
if(s>0){
shmaddress[s-1]=0;
Signal(fd);
if(strcmp(shmaddress,"quit")==0)break;
}
}
CloseFifo(fd);
sleep(2);
int n=shmdt(shmaddress);
if(n<0){
log("deatch shm failed",Error)<<"client shmid"<<n<<endl;
exit(4);
}
log("deatch shm success",Debug)<<"client shmid"<<n<<endl;
sleep(2);
return 0;
}
common.hpp
#pragma once
#include<iostream>
#include<cstring>
#include<cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include<cstdlib>
#include<assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>
#include <ctime>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include"Log.hpp"
using namespace std;
#define PATH_NAME "/home/user_001/lesson25/shrmem"//路径
#define PROJ_ID 0x66//随机数
#define SHM_SIZE 4096//共享内存最好是page4096的整数倍
#define FIFO_NAME "./fifo"
class Init{
public:
Init(){
umask(0);
int n=mkfifo(FIFO_NAME,0666);
if(n<0){
log("Init false",Error)<<"n:"<<n<<endl;
}
log("Init success",Debug)<<"n:"<<n<<endl;
}
~Init(){
unlink(FIFO_NAME);
log("remote success",Notice)<<endl;
}
};
int OpenFIFO(std::string pathname,int flags){
int fd=open(pathname.c_str(),flags);
if(fd<0){
log("open false",Error)<<"fd:"<<fd<<endl;
}
log("open success",Debug)<<"fd:"<<fd<<endl;
return fd;
}
void Wait(int fd){
log("wait....",Notice)<<endl;
uint32_t tmp=0;
ssize_t s=read(fd,&tmp,sizeof(uint32_t));
}
void Signal(int fd)
{
uint32_t temp = 1;
ssize_t s = write(fd, &temp, sizeof(uint32_t));
assert(s == sizeof(uint32_t));
(void)s;
log("唤醒中....", Notice) << "\n";
}
void CloseFifo(int fd)
{
close(fd);
}
Init init;
Log.hpp(日志信息)
#include"common.hpp"
#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3
const std::string msg[] = {
"Debug",
"Notice",
"Warning",
"Error"
};
std::ostream &log(std::string message,int level){
std::cout<<"|"<<(unsigned)time(nullptr)<<"|"<<msg[level]<<"|"<<message<<std::endl;
return std::cout;
}
Makefile
.PHONY:all
all: server client
server:memserver.cc
g++ -o $@ $^ -std=c++11
client:memclient.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f server client