【linux操作系统】进程间通信--管道
进程间通信的本质是让不同的进程看到同一份资源。而管道就在此处作同一份资源。
管道是用来进程间通信的一种方式。可以将管道理解为一个内核级缓冲区,用来暂时存储进程间的数据。linux下一切皆文件,通过write,read等系统调用来访问管道中的数据。
管道的特点
1.单项通信,即只允许一方说话。
2.管道是面向字节流的。简单来说就是输入进管道的东西不一定会一次性读出来,自己可以规定读取的边界。
3.管道的生命周期随进程,本质是文件。
4.管道通信对于多进程而言自带同步和互斥。
匿名管道
匿名管道本质上来说就是访问这一处的资源不需要路径+文件名。只能被用来没有血缘关系间的进程间的通信。
创建方法
pipefd[0],pipefd[1]分别为读端和写端。
int pipefd[2]; pipe(pipefd);父子进程利用匿名管道通信有两个约束点,同步和互斥,同步的意思是访问管道要有顺序性,互斥的意思是同一时间只能有一个人访问资源,会有四种情况出现。(以子写父读为例)
1.子进程写的慢,父进程阻塞,给写机会。
2.父进程读的慢,写满后子进程阻塞,给读机会。
3.读端在读,写端关闭,读完管道内容后read返回0。
4.写端在写,读端关闭,os直接杀死子进程。
下面给出一个进程池的demo
#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。
#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; } };完。。。
