目录
进程间通信 的本质是让不同的进程看到同一份资源。而管道就在此处作同一份资源。
管道是用来进程间通信的一种方式。可以将管道理解为一个内核级缓冲区,用来暂时存储进程间的数据。linux下一切皆文件,通过write,read等系统调用来访问管道中的数据。
管道的特点
1.单项通信,即只允许一方说话。
2.管道是面向字节流的。简单来说就是输入进管道的东西不一定会一次性读出来,自己可以规定读取的边界。
3.管道的生命周期随进程,本质是文件。
4.管道通信对于多进程而言自带同步和互斥。
匿名管道
匿名管道本质上来说就是访问这一处的资源不需要路径+文件名。只能被用来没有血缘关系间的进程间的通信。
创建方法
pipefd[0],pipefd[1]分别为读端和写端。
cpp
int pipefd[2];
pipe(pipefd);
父子进程利用匿名管道通信有两个约束点,同步和互斥,同步的意思是访问管道要有顺序性,互斥的意思是同一时间只能有一个人访问资源,会有四种情况出现。(以子写父读为例)
1.子进程写的慢,父进程阻塞,给写机会。
2.父进程读的慢,写满后子进程阻塞,给读机会。
3.读端在读,写端关闭,读完管道内容后read返回0。
4.写端在写,读端关闭,os直接杀死子进程。
下面给出一个进程池的demo
cpp
#include <iostream>
#include <unistd.h>
#include <string.h>
#include <cstdio>
#include <vector>
#include <functional>
#include <time.h>
#include <stdlib.h>
#include <sys/wait.h>
//////////////////////////////////////////////////////////////任务
typedef void (*mytask)(void); // 函数指针
void Sendmsg()
{
std::cout << getpid() << " : 发送信息!!" << std::endl;
return;
}
void Fflush()
{
std::cout << getpid() << " : 刷新刷新!!" << std::endl;
return;
}
void Sleep()
{
std::cout << getpid() << " : 休眠一下!!" << std::endl;
return;
}
void OpenAi()
{
std::cout << getpid() << " : 打开ai软件!!" << std::endl;
return;
}
void Close()
{
std::cout << getpid() << " : 关机 !!!" << std::endl;
return;
}
// 函数指针数组
mytask cb_task[5] = {Sendmsg, Fflush, Sleep, OpenAi, Close};
//////////////////////////////////////////////////////////////进程池
enum
{
OK = 0,
pipe_err,
fork_err
};
const int gprocessnum = 5; // 五个子进程
// typedef std::function<void (int)> task;
using task_t = std::function<void(int)>; // 给函数类型重命名,这个函数要返回值是void,参数是int
void Dotask(int fd)
{
// do task
while (true)
{
int intask = 0;
size_t s = read(fd, &intask, sizeof(intask));
if (s == sizeof(intask))
{
if (intask <= 4 && intask >= 0)
std::cout << intask << " ";
cb_task[intask]();
}
else if (s == 0)
{
break;
}
}
close(fd);
exit(0);
// sleep(100);
}
// 进程池
class proccess_pool
{
public:
proccess_pool()
{
}
~proccess_pool()
{
}
void Init(task_t cb)
{
creat_process_channel(cb);
}
void Debug()
{
for (auto &ch : channels)
{
ch.print_info();
}
}
void run()
{
int count = 5;
while (count)
{
// 选择一个任务
int intask = select_task();
// 选择一个进程
int intpro = select_process();
// 将任务发送给进程
send_task2process(intask, intpro);
sleep(1);
count--;
}
}
void quit()
{
for (auto &ch1 : channels)
{
ch1.channel_close();
}
for (auto &ch2 : channels)
{
ch2.pro_wait();
}
return;
}
private:
class Channel
{
public:
Channel(int wfd, pid_t id)
: _wfd(wfd), _id(id)
{
_sub_name = "channel_name : " + std::to_string(_id);
}
~Channel()
{
}
void print_info()
{
printf("写端编号:%d, 子进程:%d, %s\n", _wfd, _id, _sub_name.c_str());
}
int Re_wfd()
{
return _wfd;
}
void channel_close()
{
printf("关闭wfd:%d\n", _wfd);
close(_wfd);
}
void pro_wait()
{
printf("子进程回收:%d\n", _id);
sleep(1);
pid_t rid = waitpid(_id, nullptr, 0);
(void)rid;
}
private:
int _wfd; // 记录打开的管道的写端
pid_t _id; // 以及对应的子进程的pid
std::string _sub_name;
};
void creat_process_channel(task_t cb)
{
for (int i = 0; i < gprocessnum; i++)
{
// 创建管道
int pipefd[2] = {0};
int n = pipe(pipefd);
if (n < 0)
{
std::cerr << "pipe errro" << std::endl;
exit(pipe_err); // 不用return不用返回
}
// 创建子进程
pid_t id = fork();
if (id < 0)
{
std::cerr << "fork errro" << std::endl;
exit(fork_err);
}
else if (id == 0)
{
close(pipefd[1]); // 子进程读取,关闭写端
// Dotask(pipefd[0]);子进程只能干一件事
// 关闭子进程多余的写端
for (auto &ch : channels)
{
close(ch.Re_wfd());
}
cb(pipefd[0]); // 回调函数 cb就是Dotask 以后可以通过初始化来给子进程传递不同的任务
exit(0);
}
else
{
close(pipefd[0]);
// Channel ch(pipefd[1], id);
// channels.push_back(ch);
printf("子进程创建成功!!pid : %d\n", id);
sleep(1);
channels.emplace_back(pipefd[1], id);
}
}
}
// 采用轮询的方式选择进程
int select_process()
{
static int n = 0; // 这行命令只会执行一次
n++;
n %= 5;
return n;
}
int select_task()
{
srand(time(nullptr));
int index = rand() % 5; // 将会有五个任务
return index; // 返回一个0-4的整数
}
void Write(int a, int b)
{
// 第几个进程就对应着第几个通道嘛
int n = write(channels[b].Re_wfd(), &a, sizeof(a)); // 规定一次写4字节
(void)n;
}
// 给子进程发送任务
void send_task2process(int a, int b) // a是任务编号,b是进程编号
{
// 这是父进程写的过程
// 写过去的也只会是任务的编号
Write(a, b);
return;
}
std::vector<Channel> channels;
};
int main()
{
proccess_pool pp;
pp.Init(Dotask);
pp.Debug();
pp.run();
pp.quit();
return 0;
}
命名管道
命名管道与匿名管道相对,命名管道有自己的路径+文件名。用于两个完全不相关的进程间发生通信。文件相关的核心结构只会存在一份。管道文件里面的内容只会存在于缓冲区,不会刷新到磁盘上。
使用int mkfifo(const char *filename,mode_t mode)函数创建命名管道。第一个参数表示管道名称,第二个参数表示管道权限。同样是用write,read等系统调用对管道进行访问。
下面是一段利用命名管道通信的demo。
cpp
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <errno.h>
#include <cerrno>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define SIZE 1024
const char *name = "./pipe";
#define READ_OPEN 1 // 0001
#define WRITE_OPEN 2 // 0010
class namepipe
{
public:
namepipe(const std::string name_pipe = name, mode_t mode = 0666)
: _pipe_name(name_pipe), _mode(mode), _fd(-1)
{
}
void Build()
{
umask(0);
if (Isexit())
{
std::cout << "管道已经存在!!!" << std::endl;
return;
}
// 文件不存在就创建
int p = mkfifo(_pipe_name.c_str(), _mode);
if (p < 0)
{
std::cerr << "mkfifo error : " << strerror(errno) << " errno " << errno << std::endl;
}
else
{
std::cout << "管道创建成功!!!" << std::endl;
}
}
void Open(int flag)
{
// 读方式打开
if (flag & READ_OPEN)
{
std::cout << "开始打开文件" << std::endl;
_fd = open(_pipe_name.c_str(), O_RDONLY);
if (_fd < 0)
{
std::cerr << "open error : " << strerror(errno) << " errno " << errno << std::endl;
std::cout << "文件打开失败!!" << std::endl;
exit(-1);
}
char outbuffer[SIZE];
while (true)
{
ssize_t s = read(_fd, outbuffer, sizeof(outbuffer) - 1);
if (s > 0)
{
std::cout << outbuffer << std::endl;
}
else if (s == 0)
{
close(_fd);
break;
}
}
}
// 以写方式打开
else if (flag & WRITE_OPEN)
{
_fd = open(_pipe_name.c_str(), O_WRONLY);
if (_fd < 0)
{
std::cout << "文件打开失败!!" << std::endl;
exit(-1);
}
int cnt = 10;
while (cnt--)
{
std::cout << "Please write your msg :";
std::string inbuffer;
std::getline(std::cin, inbuffer);
int n = write(_fd, inbuffer.c_str(), inbuffer.size());
// std::cout << inbuffer << std::endl;
}
close(_fd);
}
else
{
}
}
void Delete()
{
if (!Isexit())
{
return;
}
// 存在就删除
int n = unlink(_pipe_name.c_str());
if (n == 0)
{
std::cout << "成功删除" << std::endl;
}
else
{
std::cerr << "unlink error : " << strerror(errno) << " errno " << errno << std::endl;
}
}
~namepipe()
{
}
private:
const std::string _pipe_name;
mode_t _mode;
pid_t _fd;
bool Isexit(const char *name = "./pipe")
{
struct stat buffer;
int n = stat(name, &buffer);
if (n == 0)
return true;
else
return false;
}
};
完。。。