青岛seo公司网站,网站没有访问量,网站建设费用说明,太原网站建设平台进程间通信之管道 文章目录 进程间通信之管道1. 进程间通信1.1 为什么要进行进程间的通信1.2 如何进行进程间的通信1.3 进程间通信的方式 2. 管道2.1 匿名管道2.1.1 系统调用pipe()2.1.2 使用匿名管道进行通信2.1.1 匿名管道四种情况2.1.2 匿名管道的五大特性2.1.3 进程池 2.2 …进程间通信之管道 文章目录 进程间通信之管道1. 进程间通信1.1 为什么要进行进程间的通信1.2 如何进行进程间的通信1.3 进程间通信的方式 2. 管道2.1 匿名管道2.1.1 系统调用pipe()2.1.2 使用匿名管道进行通信2.1.1 匿名管道四种情况2.1.2 匿名管道的五大特性2.1.3 进程池 2.2 命名管道2.2.1 创建命名管道2.2.2 利用命名管道进行进程间通信2.2.3 命名管道的一个特性 1. 进程间通信
1.1 为什么要进行进程间的通信
对于这个问题答案显而易见一个进程必然不能解决所有的问题系统中往往需要多个进程的协作来进行工作而进程间的协作就需要进程之间进行信息的交互这个过程也叫做进程间的通信
简单来说进程间的通信可以实现以下功能 数据传输进程控制资源共享事件通知……………… 1.2 如何进行进程间的通信
由于进程具有独立性因此两个进程之间不能直接进行数据之间的传输否则就会破坏进程的独立性
因此我们需要一片公共的区域来供进程之间进行信息交流而这片公共区域就是由OS操作系统提供
根据上面的分析我们也可以得出关于进程间通信的本质 进程间通信的实质上就是让不同的进程看到同一份资源 1.3 进程间通信的方式
根据操作系统OS提供公共区域方式的不同进程间的通信也会有不同的形式例如
管道共享内存信号量消息队列
2. 管道
我们来假设这样一个场景
一个进程同时以读写的方式打开一个文件那么就会产生两个文件描述符fd 此时创建子进程子进程会继承父进程属性 我们可以发现这时子进程和父进程就同时看到同一份文件file了。这时如果我们关闭父进程的写端再关闭子进程的读端 这样就可以通过子进程向公共文件写数据父进程向公共文件读数据的方式进行父子进程之间的通信了。 向上面这样的基于文件的形式进行进程间通信的方式叫做管道
通过上面的例子我们也知道管道只允许一端读一端写因此基于管道的通信都是单向的
管道可以分为匿名管道和命名管道
2.1 匿名管道
2.1.1 系统调用pipe()
系统不允许用磁盘文件当作进程间通信的公共资源为此系统提供了系统调用pipe()来为我们创建进程通信需要的文件 #include unistd.h
int pipe(int pipefd[2]);该系统调用会创建并以读写方式打开一个不存在于磁盘且不需要向磁盘刷新的只存在于内存中的匿名文件因为这个文件只需用来给两个进程进行通信不需要持久的保存这个文件就叫做匿名管道数组pipefd是一个输出型参数用于存放匿名管道的fd其中pipefd[0]存放的是读端pipefd[1]存放的是写端
2.1.2 使用匿名管道进行通信
使用pipe()创建了系统调用后我们再创建一个子进程让子进程继承父进程的数据此时父子进程就会同时以读写的方式指向同一个匿名管道了。最后只要确定数据传输方向关闭父进程的读写端关闭子进程的写读端就可以正常进程进程间的通信了
同时我们应该清楚 由于是通过创建子进程的方式来让子进程继承父进程的数据使其指向相同的匿名管道来进行通信因此匿名管道只能用来进行血缘进程之间的通信通常用于父子进程 示例
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include sys/types.h
#include wait.hvoid my_write(int wfd) //写端操作
{const char* str i am child process: ;int cnt 0;char buf[128] {0};pid_t id getpid();while (cnt ! 20){snprintf(buf, sizeof(buf), message: %s, id: %d, cnt: %d\n, str, id, cnt);write(wfd, buf, strlen(buf));sleep(1);}
}void my_read(int rfd) //读端操作
{char buf[1024] {0};int cnt 20;while(cnt--){read(rfd, buf, sizeof(buf) - 1); //-1预留\0防止出现错误printf(%s\n, buf);}
}int main()
{int pipefd[2] {0}; int ret pipe(pipefd); //创建匿名管道if (-1 ret){perror(pipe:);return 1;}pid_t id fork();if (0 id) //子进程{//chile(w)close(pipefd[0]); //关闭读端my_write(pipefd[1]); //进行写操作exit(-1);}//father(r)close(pipefd[1]); //关闭写端my_read(pipefd[0]); //进行读操作wait(NULL); //等待子进程退出防止出现僵尸进程return 0;
}效果如图所示 2.1.1 匿名管道四种情况
情况一写端停止写入并且管道没有数据那么读端就会阻塞直到读到数据 例如将上述代码的my_write()函数修改 void my_write(int wfd)
{//写端不写
}重新编译后运行效果如图 可以看到如果写端不向读端写入数据并且管道没有数据读端就会陷入阻塞等待状态 情况二写端一直在写直到管道写满读端不读那么写端就会阻塞等待直到管道的数据被读取 例如将上述代码的my_write()、my_read()函数修改 void my_write(int wfd)
{int cnt 0;char a A;//每次只写入一个字符并输出输入的字符个数while(1){write(wfd, a, 1);printf(cnt: %d\n, cnt);}
}void my_read(int rfd)
{//读端不读
}重新编译后运行效果如图 可以看到当读端不读时写端向管道写入了65535 1个字符后就进入了阻塞等待状态这说明管道已经被填满了。同时也可以推出在博主所用的系统中默认的管道大小为65536Byte也就是64kB 如果我们再次修改代码让读端每2秒读取一次管道 void my_read(int rfd)
{char buf[1024] {0};while(1){sleep(2);int n read(rfd, buf, sizeof(buf) - 1);if (0 n)break;printf(%s\n, buf);}
}重新编译后运行效果如图 可以总结当管道的部分数据被读取后写端有重新写入数据了 情况三写端关闭当读端读完管道的数据后读端就会读到管道的末尾相当于读到文件尾自动关闭读端 例如将上述代码的my_write()、my_read()函数修改 void my_write(int wfd)
{const char* str i am child process: ;int cnt 0;char buf[128] {0};pid_t id getpid();while (cnt ! 5){snprintf(buf, sizeof(buf), message: %s, id: %d, cnt: %d\n, str, id, cnt);write(wfd, buf, strlen(buf));sleep(1);}
}void my_read(int rfd)
{char buf[1024] {0};while(1){int n read(rfd, buf, sizeof(buf) - 1);if (0 n) //如果read的返回值为0说明写端关闭了fd读端读到了管道末尾{printf(no data be read, read exit\n);break;}printf(%s\n, buf);}
}重新编译后运行效果如图 情况四读端关闭写端还在写系统就会通过发送信号的方式强制终止写端(kill -13 child_pid) 将整体代码修改如图 #include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include sys/types.h
#include wait.hvoid my_write(int wfd)
{const char* str i am child process: ;char buf[128] {0};pid_t id getpid();while (1){snprintf(buf, sizeof(buf), message: %s, id: %d\n, str, id);write(wfd, buf, strlen(buf));sleep(1);}
}void my_read(int rfd)
{char buf[1024] {0};int cnt 5;while(cnt--){int n read(rfd, buf, sizeof(buf) - 1);if (0 n){printf(no data be read, read exit\n);break;}printf(%s\n, buf);}printf(read will close\n);sleep(1);close(rfd);
}int main()
{int pipefd[2] {0};int ret pipe(pipefd);if (-1 ret){perror(pipe:);return 1;}pid_t id fork();if (0 id){//chile(w)close(pipefd[0]);my_write(pipefd[1]);printf(child close wfd\n\n);close(pipefd[1]);exit(-1);}//father(r)close(pipefd[1]);my_read(pipefd[0]);int status 0;int rid waitpid(id, status, 0);if (rid id){printf(child process singal code: %d\n, status 0x7f);}return 0;
}重新编译后运行效果如图 2.1.2 匿名管道的五大特性
同步机制管道在处理数据读写时确保数据的有序性和正确性的一种控制方式血缘进程通信由于匿名管道的构建建立在进程继承的基础上因此匿名管道只允许血缘进程的通信单向通信半双工同一时间只允许一端读一端写文件的生命周期是随进程的父子进程退出管道文件自动释放pipe是面向字节流的
2.1.3 进程池
进程池的基本概念 进程池是一组预先创建好的进程集合这些进程处于空闲等待状态随时准备接收任务并进行处理。父进程可以在任意时候控制子进程的休眠、工作与退出。 进程池的优点 可以复用进程从而避免了频繁的调用系统函数节省了资源开销与时间使用进程池有利于管理并充分利用系统资源 实现进程池
进程池程序myprocesspool的程序流程大致如下 实现代码 task.hpp: #pragma once#includeiostream
#includeunistd.htypedef void(*work_t)(pid_t);
typedef void(*task_t)(pid_t);void task_1(pid_t id)
{std::cout task_1 std::endl;
}void task_2(pid_t id)
{std::cout task_2 std::endl;
}void task_3(pid_t id)
{std::cout task_3 std::endl;
}task_t tasks[3] {task_1, task_2, task_3};void worker(int channel_index)
{while(1){int task_index 0;int n read(0, task_index, sizeof(int));if (0 n){std::cout write close, channel: channel_index closing………… std::endl; break;}std::cout channel: channel_index pid: getpid() : i am working: ;tasks[task_index](getpid());}
}processpool.cc: #include iostream
#include string
#include vector
#include unistd.h
#include sys/types.h
#include sys/wait.h
#include ctime
#include task.hpp#define TIME 5//return value
enum
{USAGE_ERROR 1,PROCESS_NUM_ERROR, PIPE_ERROR,SELECT_CHANNEL_ERROR,SELEDT_TASK_ERROR
};void usage()
{std::cout Usage: ./processpool [process_num] std::endl;
}class channel
{
public:channel(std::string name, int rfd, int wfd, pid_t child_id): _name(name), _rfd(rfd), _wfd(wfd), _child_id(child_id){}const std::string get_name(){return _name;}int get_rfd(){return _rfd;}int get_wfd(){return _wfd;}pid_t get_child_id(){return _child_id;}void close_fd(){close(_wfd);}
private:const std::string _name;const int _rfd;const int _wfd;const pid_t _child_id;
};class processpool
{
public:processpool(int process_num): _process_num(process_num){}//创建管道int create_channel(){std::vectorint fd;for (int i 0; i _process_num; i){int pipefd[2] {0};int ret pipe(pipefd);if (-1 ret){perror(pipe:);return PIPE_ERROR;}pid_t id fork();if (id 0){//child: readclose(pipefd[1]);dup2(pipefd[0], 0);//关闭除第一个子进程外的多个写端if (!fd.empty()){for (auto k : fd)close(k);}//workworker(i);std::cout channel i pid: getpid() exit std::endl;exit(-1);}//father: writeclose(pipefd[0]);std::string name std::string(channel: ) std::to_string(i);channel c channel(name, pipefd[0], pipefd[1], id);_channels.push_back(c);fd.push_back(pipefd[1]);}return 0;} int select_channel(){static int c 0;int ret c;c (c) % _process_num;return ret;}int select_task(){return rand() % 3;}int control_child(){int cnt TIME;while(cnt--){//选择一个进程(管道)一个任务int channel_index select_channel();int task_index select_task();if (channel_index _process_num){std::cout select channel error std::endl;return SELECT_CHANNEL_ERROR;}if (task_index 3){std::cout select task error std::endl;return SELEDT_TASK_ERROR;}//发送任务int wfd _channels[channel_index].get_wfd();write(wfd, task_index, sizeof(int));sleep(2);}return 0;}void PrintDebug(){for (auto channel: _channels){std::cout channel.get_name() : rfd: channel.get_rfd() : child_id: channel.get_child_id() std::endl;}}void clean_wait(){//关闭所有的写端for (auto channel : _channels){channel.close_fd();pid_t id waitpid(channel.get_child_id(), nullptr, 0);if (-1 id){perror(waitpid:);}if(id channel.get_child_id()){std::cout wait child: channel.get_child_id() success\n std::endl;}}}
private:const int _process_num;std::vectorchannel _channels;
};// ./process num
int main(int argc, char* argv[])
{if (1 argc){usage();return USAGE_ERROR;}int process_num std::stoi(argv[1]);if (process_num 0){std::cout process_num should be grearter than 0 std::endl;return PROCESS_NUM_ERROR;}srand((unsigned int)time(nullptr));std::cout process nums: process_num std::endl;//创建进程池processpool* processpool_1 new processpool(process_num);//创建管道int ret processpool_1-create_channel(); if (0 ! ret){return ret;}std::cout create channels complete std::endl; //工作ret processpool_1-control_child();if (0 ! ret){return ret;}//回收 processpool_1-clean_wait();return 0;
}2.2 命名管道
上文提到匿名管道只适用于血缘进程之间的通信那么为了解决没有关系进程间通信的问题操作系统提供了命名管道
2.2.1 创建命名管道
使用命令创建命名管道
mkfifo [filename]例如 可以看到通过命令mkfifo fifo在当前目录创建了一个名为fifo的管道文件同时可以看到这个管道文件的文件类型为p(pipe)
使用系统调用创建命名管道
int mkfifo(const char *pathname, mode_t mode);pathname是管道文件的文件名mode是管道文件的权限返回值成功返回0失败返回-1 2.2.2 利用命名管道进行进程间通信
知道了系统调用后我们就可以利用管道来进行进程间的通信了
我们用下面的代码进行演示
头文件
#include iostream
#include string
#include cstring
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h#define PATH FIFO //默认文件名
#define MODE 0666 //默认权限//命名管道类
class fifo
{
public:fifo(const std::string name) //构造函数创建管道文件: _name(name){int ret mkfifo(_name.c_str(), MODE);if (-1 ret){std::cerr mkfifo error, errno: errno , errorstring: strerror(errno) std::endl;exit(-1);}std::cout fifo made success std::endl;}~fifo(){unlink(_name.c_str()); //进程结束时利用系统调用unlink()删除管道文件}
private:const std::string _name;
};服务端server读
#include common.hppint main()
{std::cout I am server std::endl; fifo named_pipe(PATH); //服务端创建管道文件读端int rfd open(PATH, O_RDONLY); char buffer[1024] {0};while (1){int n read(rfd, buffer, sizeof(buffer) - 1);if (0 n) //返回值为0说明读到文件尾即写端关闭{std::cout client exit, server also exit std::endl;break;}else if (-1 n){std::cerr read error, errno: errno , errorstring: strerror(errno) std::endl;exit(-1);}else{buffer[n] 0;std::cout buffer std::endl;}}close(rfd);return 0;
}客户端client写
#include common.hppint main()
{std::cout I am client std::endl;int wfd open(PATH, O_WRONLY); //客户端写std::string buffer;while(1){printf(message # );std::getline(std::cin, buffer);if (quit buffer){printf(client exit\n);break;}int n write(wfd, buffer.c_str(), buffer.size());if (n ! buffer.size()){std::cerr write error, errno: errno , errorstring: strerror(errno) std::endl;exit(-1);}}close(wfd);return 0;
}效果如图 2.2.3 命名管道的一个特性
如果我们在服务端server.cc代码中加一行代码
#include common.hppint main()
{std::cout I am server std::endl;fifo named_pipe(PATH);int rfd open(PATH, O_RDONLY);std::cout open success std::endl; //查看客户端是否成功打开管道文件//……………………}并运行服务端读端一段时间后再打开客户端写端 可以看到在服务端读端打开到客户端写端未打开的这段时间中服务端读端并没有打开管道文件而是等客户端写端启动后再打开的管道。
通过这个现象我们可以得出命名管道的一个特性 当写端未打开而读端打开时读端会阻塞直至写端也打开 本篇完