1.进程间通信的目的
进程具有独立性,因此进程间想要通信,成本会非常高,但有时候又需要多个进程协同处理一件事,所以进程间通信是必不可少的
2.管道
从一个进程连接到另一个进程的一个数据流称为一个"管道"
2.1 匿名管道

显示器是缺乏管道控制的,所以父子进程在向显示写入的时候是无序的,而管道是自带访问控制机制的,如果管道内部没有数据,reader就必须阻塞等待;如果管道内部被写满了,writer就必须阻塞等待
代码:
#include<iostream>
#include<unistd.h>
#include<cstring>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
int main()
{
//1.创建管道
int pipefd[2] = {0};
if(pipe(pipefd) != 0)//创建一个管道成功返回0
{
cerr<<"pipe error\n"<<endl;
return 1;
}
//cout<<pipefd[0]<<" "<<pipefd[1]<<endl; pipefd[0] 是读端的文件描述符 pipefd[1] 是写端的文件描述符
//2.创建子进程
pid_t id = fork();
if(id < 0)
{
cerr<<"fork error" <<endl;
return 2;
}
else if(id == 0)
{
//child
//子进程读取数据,关闭写端
close(pipefd[1]);
char buffer[1024];
while(true)
{
//清空缓冲区
memset(buffer,0,sizeof(buffer));
ssize_t s = read(pipefd[0],buffer,sizeof(buffer) - 1);//返回读到字节的个数
if(s > 0)
{
//读取成功
buffer[s] = '\0';
cout<<"子进程收到消息,内容是:"<<buffer<<endl;
}
else if(s == 0)
{
cout<<"父进程写完了,我也退出了"<<endl;
break;
}
else
{}
}
close(pipefd[0]);
exit(0);
}
else
{
//parent
//父进程来进行写入,关闭读端
close(pipefd[0]);
const char *msg = "你好子进程,我是父进程,这次发送的信息编号是:";
int cnt = 0;
while(cnt < 5)
{
sleep(1);
char sendBuffer[1024];
sprintf(sendBuffer, "%s : %d",msg, cnt);
write(pipefd[1],sendBuffer,strlen(sendBuffer));
cnt++;
}
close(pipefd[1]);
cout<<"父进程写完了"<<endl;
}
pid_t res = waitpid(id,nullptr,0);
if(res > 0)
{
cout<<"等待子进程成功"<<endl;
}
return 0;
}
进程池:通过父进程控制一批子进程
#include<iostream>
#include<unistd.h>
#include<cstring>
#include<sys/types.h>
#include<sys/wait.h>
#include<vector>
#include<ctime>
#include<unordered_map>
#include<cassert>
#include<stdlib.h>
using namespace std;
typedef void (*functor)();//函数指针
vector<functor> functors;//方法集合
unordered_map<uint32_t,string> info; //uint32_t = unsigned int
void f1()
{
cout<<"这是一个处理日志的任务,执行进程ID ["<<getpid()<<
"] 执行时间是 [" << time(nullptr) << "]\n"<<endl;
}
void f2()
{
cout<<"这是一个备份数据任务,执行进程ID ["<<getpid()<<
"] 执行时间是 [" << time(nullptr) << "]\n"<<endl;
}
void f3()
{
cout<<"这是一个处理网络链接的任务,执行进程ID ["<<getpid()<<
"] 执行时间是 [" << time(nullptr) << "]\n"<<endl;
}
void loadFunctor()
{
info.insert({functors.size(),"处理日志的任务"});
functors.push_back(f1);
info.insert({functors.size(),"备份数据的任务"});
functors.push_back(f2);
info.insert({functors.size(),"处理网络链接的任务"});
functors.push_back(f3);
}
//pair<进程pid,进程对应的管道写端fd>
typedef pair<int32_t,int32_t> elem;
int processNum = 5;
void work(int blockFd)
{
cout<<"进程["<<getpid()<<"]"<<"开始工作"<<endl;
while(true)
{
//a.阻塞等待 b.获取任务信息
uint32_t operatorCode = 0;
ssize_t s = read(blockFd,&operatorCode,sizeof(uint32_t));
if(s == 0) break;
assert(s == sizeof(uint32_t));//读到的不是四个字节报错
(void)s;
//c.处理任务
if(operatorCode < functors.size()) functors[operatorCode]();
}
cout<<"进程["<<getpid()<<"]"<<"结束工作"<<endl;
}
void blanceSendTask(const vector<elem> &processFds)
{
srand((long long)time(nullptr));
while(true)
{
sleep(1);
//随机选择一个子进程
//较为均匀的将任务给所有的子进程 ---- 负载均衡
uint32_t pick = rand() % processFds.size();
//选择一个任务
uint32_t task = rand() % functors.size();
//把任务给一个指定的进程
write(processFds[pick].second,&task,sizeof(task));
//打印对应的提示信息
cout<<"父进程指派任务->"<<info[task]<<"给进程:"<<processFds[pick].first<<"编号:"<<pick<<endl;
}
}
int main()
{
loadFunctor();
vector<elem> assignMap;
//创建processNum个进程
for(int i = 0;i < processNum; i++)
{
//定义保存管道fd的对象
int pipefd[2] = {0};
//创建管道
pipe(pipefd);
//创建子进程
pid_t id = fork();
if(id == 0)
{
//子进程读取,r->pipefd[0]
close(pipefd[1]);
//子进程执行
work(pipefd[0]);
close(pipefd[0]);
exit(0);
}
//父进程做的事情
close(pipefd[0]);
elem e(id,pipefd[1]);
assignMap.push_back(e);
}
cout<<"create all processs success!"<<endl;
//父进程派发任务
blanceSendTask(assignMap);
//回收资源
for(int i = 0; i < processNum;i++)
{
if(waitpid(assignMap[i].first,nullptr,0) > 0)
{
cout << "wait for: pid="<<assignMap[i].first<<"wait success!"<<"number:"<<i<<'\n';
close(assignMap[i].second);
}
}
return 0;
}
管道的特征
1、管道只能用来进行具有血缘关系的进程之间,进行进程间通信,常用于父子通信
2、管道只能单向通信
3、管道自带同步机制(pipe满,writer等;pipe空,reader等)
4、管道是面向字节流的 ---- 先写的字符一定是先被读取的,没有格式边界,需要用户规定读多少字节
5、管道的生命周期 ---- 随进程
2.2 命名管道
继承了匿名管道的所有特性,并且允许两个没有血缘关系的进程进行通信

匿名管道是通过子进程继承父进程实现的,而命名管道是通过不同进程访问同一文件(fifo文件)实现的
//comm
#pragma once
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cerrno>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define IPC_PATH "./.fifo"
//读取 serverFifo
#include "comm.h"
using namespace std;
#define NUM 1024
int main()
{
umask(0);
if(mkfifo(IPC_PATH,0600) != 0)//成功返回0
{
cerr<<"mkfifo error"<<endl;
return 1;
}
int pipeFd = open(IPC_PATH,O_RDONLY);
if(pipeFd < 0)
{
cerr<<"open fifo error"<<endl;
return 2;
}
char buffer[NUM];
while(true)
{
ssize_t s = read(pipeFd,buffer,sizeof(buffer)-1);
if(s > 0)
{
buffer[s] = '\0';
cout<<"客户端->服务器#"<<buffer<<endl;
}
else if(s == 0)
{
cout<<"客户端退出了,我也退出吧"<<endl;
break;
}
else
{
cout<<"read: "<<strerror(errno)<<endl;
break;
}
}
close(pipeFd);
cout<<"服务器端退出"<<endl;
unlink(IPC_PATH);//删除创建的.fifo文件
return 0;
}
//写入 clientFifo
#include "comm.h"
using namespace std;
int main()
{
int pipeFd = open(IPC_PATH,O_WRONLY);
if(pipeFd < 0)
{
cerr<<"open:"<<strerror(errno)<<endl;
return 1;
}
#define NUM 1024
char line[NUM];
while(true)
{
cout<<"请输入你的信息#";
fflush(stdout);
memset(line, 0, sizeof(line));
if(fgets(line,sizeof(line),stdin) != nullptr)
{
line[strlen(line)-1] = '\0'; //去掉输入时的回车
write(pipeFd,line,strlen(line));
}
else{
break;
}
}
close(pipeFd);
cout<<"客户端退出"<<endl;
return 0;
}
3.共享内存
创建共享内存 shmget

key可以自己设置人任意值,只要保证它的唯一性就好,但是一般都是用ftok()来进行设置
删除共享内存 shmctl

shmctl(shmid, IPC_RMID,nullptr)
使用共享内存 shmat 成功返回共享内存的地址,失败返回-1
取消关联 shmdt
ipc 命令:
ipcs -m : 显示当前用户创建的共享内存
ipcrm -m + shmid: 删除共享内存
共享内存属于双方的用户空间(堆、栈),可以直接访问共享内存,不用使用系统接口,但是没有任何访问控制,不安全
用管道来对共享内存增加访问控制
//comm
#pragma once
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cerrno>
#include<cassert>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include"Log.hpp"
#include<sys/stat.h>
#include<fcntl.h>
#include<string>
#include<unistd.h>
#include<sys/shm.h>
#define PATH_NAME "/home/ldx/code/2022_11_10"
#define PROJ_ID 0x14
#define MEM_SIZE 4096
#define FIFO_FILE ".fifo"
key_t CreateKey()
{
key_t key = ftok(PATH_NAME,PROJ_ID);
if(key < 0)
{
std::cerr << "ftok:" << strerror(errno) << std::endl;
exit(1);
}
return key;
}
void CreateFifo()
{
umask(0);
if(mkfifo(FIFO_FILE,0666) < 0) //创造管道文件 ".fifo"
{
Log() << strerror(errno) << '\n';
exit(2);
}
}
#define READER O_RDONLY
#define WRITER O_WRONLY
int Open(const std::string& filename,int flags)
{
return open(filename.c_str(),flags);
}
int Wait(int fd)
{
uint32_t values = 0;
ssize_t s = read(fd, &values, sizeof(values));
return s;
}
int Signal(int fd)
{
uint32_t cmd = 1;
write(fd, &cmd, sizeof(cmd));
}
int Close(int fd, const std::string filename)
{
close(fd);
unlink(filename.c_str());
}
//Log
#pragma once
#include<iostream>
#include<ctime>
std::ostream& Log()
{
std::cout<<"for debuge |" <<"timestamp"<<(uint64_t)time(nullptr)<<"|";
return std::cout;
}
//Cli
#include "Comm.hpp"
#include "Log.hpp"
#include <cstdio>
#include <unistd.h>
using namespace std;
// 充当使用共享内存的角色
int main()
{
int fd = Open(FIFO_FILE, WRITER);
// 创建相同的key值
key_t key = CreateKey();
Log() << "key: " << key << "\n";
// 获取共享内存
int shmid = shmget(key, MEM_SIZE, IPC_CREAT);
if (shmid < 0)
{
Log() << "shmget: " << strerror(errno) << "\n";
return 2;
}
// 挂接
char *str = (char*)shmat(shmid, nullptr, 0);
while(true)
{
printf("Please Enter# ");
fflush(stdout);
ssize_t s = read(0, str, MEM_SIZE);
if(s > 0)
{
str[s] = '\0';
}
Signal(fd);//标记写完了
}
// 去关联
shmdt(str);
return 0;
}
//Ser
#include "Comm.hpp"
#include "Log.hpp"
#include <unistd.h>
using namespace std;
// 我想创建全新的共享内存
const int flags = IPC_CREAT | IPC_EXCL;
// 充当使用共享内存的角色
int main()
{
CreateFifo();
int fd = Open(FIFO_FILE, READER);
assert(fd >= 0);
key_t key = CreateKey();
Log() << "key: " << key << "\n";
Log() << "create share memory begin\n";
int shmid = shmget(key, MEM_SIZE, flags | 0666);
if (shmid < 0)
{
Log() << "shmget: " << strerror(errno) << "\n";
return 2;
}
Log() << "create shm success, shmid: " << shmid << "\n";
// 1. 将共享内存和自己的进程产生关联attach
char *str = (char *)shmat(shmid, nullptr, 0);
Log() << "attach shm : " << shmid << " success\n";
// 用它
while(true)
{
// 让读端进行等待
if(Wait(fd) <= 0) break;
printf("%s\n", str);
sleep(1);
}
// 2. 去关联
shmdt(str);
Log() << "detach shm : " << shmid << " success\n";
// sleep(5);
// 删它
shmctl(shmid, IPC_RMID, nullptr);
Log() << "delete shm : " << shmid << " success\n";
Close(fd, FIFO_FILE);
// sleep(5);
return 0;
}
可以被多个程序看到的资源叫临界资源,访问临界资源的代码叫临界区
信号量就是一个计数器,这个计算器对应的操作是原子的
信号量对应的操作:
sem:-- ,申请资源:P
sem:++,释放资源,V