一、通信的概念
很容易理解就是两个进程可以相互进行数据的输入输出,对一块资源的共同使用,或者是一个进程让另外一个进程执行不同任务等都是通信的目的
我们知道,进程是有独立性的,那肯定不可能是在自己的进程内部的资源,因为其它进程看不到,所以一定是有一块公共空间,能让不同进程进行通信
二、管道通信
就是我们以前命令用过的 比如 ls | head -1 的竖划线就是管道,为什么叫管道?因为管道通信都是单向通信,一个进程只能写入数据,另外一个只能读数据像我们现实中管道一样,一边工厂给你输入水,你自己用水龙头使用。
2.1匿名管道
特点:严格的单向通信(一个进程只做输入或输出)
管道结构:它其实是一个内存级的file文件,也就是将来的数据不会刷新到磁盘上。file文件中的内核缓冲区就是管道,它与普通文件的区别就是它没有名字(也就是没有路径),其他的inode、fd等结构都有
也就是说这个file也能存在在我们进程的文件描述符数组里面,所以说,管道的创建思想是借用了文件创建的思想也就是复用内核的结构
父子进程之间的通信
我们知道,父子进程保持独立性的原因主要是一旦在代码中修改,就会发生写实拷贝,父子进程在物理内存的代码各一份,所以缺点很明显:数据只能提前搞好,一旦修改以后资源就不同
但管道通信还真是只能父子之间的,那是如何做到的呢?
所以父子进程就能共用一块资源做到通信,
如何代码实现通信呢?
那首先我们得知道,一个管道文件是被进程的两个fd指向的
那肯定要系统调用创建管道-pipe(int pipefd[ ]),需要传一个两个元素的数组,其中pipe创建管道后 fd[0]对应读端,放到fd[1]对应写端,为什么要这样,不是说单向通信吗?因为你不知道是将来的子进程做输入还是做输出,并且将来你自己是做输入输出,所以创建的子进程会拷贝父进程,那它的文件描述符数组是和父进程一样的,它也有两个fd指向管道分别为读端和写端,将来谁要做写端,那就把另外一个进程的写端给close关闭调,然后自己的读端close掉,不就是单向通信么
下面是代码的小demo:
#include<stdio.h>#include<unistd.h>#include<string.h>#include<sys/wait.h>#include<stdlib.h>intmain(){intpipefd[2]={0};intn=pipe(pipefd);// 创建管道if(n<0){perror("pipe");}printf("pipefd[0]: %d,pipefd[1]:%d\n",pipefd[0],pipefd[1]);// 0为读,1为写intid=fork();// 创建子进程if(id==0){// childclose(pipefd[0]);charoutbuff[1024];intcnt=5;char*msg="hello pipe!";while(cnt){snprintf(outbuff,sizeof(outbuff),"%s %d %d\n",msg,cnt--,getpid());// snprintf会自动在末尾加\0write(pipefd[1],outbuff,strlen(outbuff));// close(pipefd[1]);// break;sleep(1);}printf("write pip over\n");exit(0);}close(pipefd[1]);charinbuff[1024];while(1){// inbuff[0]=0;ssize_t n=read(pipefd[0],inbuff,sizeof(inbuff)-1);// 减一是给\0留位置if(n>0){inbuff[n]='\0';printf("%s",inbuff);close(pipefd[0]);}// printf("%s:%ld\n",inbuff,n);// sleep(1);elseif(n==0){printf("read over");break;}else{perror("read");break;}}// close(pipefd[0]);intstatus=0;intrid=waitpid(id,&status,0);if(rid>0){printf("%d,exit_code;%d,signal_code;%d\n",rid,WEXITSTATUS(status),status&0x7f);}return0;}下面是总结的匿名管道的特性与情况
特性:
1、管道是单向通信的
2、匿名管道是具有"血缘关系"的进程,常用父子通信(继承内核资源)
3、管道随进程结束而结束(本质就是判断管道的进程引用引用计数)
4、管道是面向字节流的(也就是管道里的数据不一定是字符,图片、音频等都有可能)
字节流的说明:也就是你往管道里写多次数据,但输出的数据不一定是全部的数据,它是按需输出,就类似于语言级缓冲区数据刷新进内核级缓冲区的特性
5、管道通信对于多进程而言,是有同步和互斥的关系
同步:就是访问数据具有一定的顺序性,也就是访问数据需要排队(这只是简单的理解)
互斥:任何时刻只允许一个进程访问资源
情况(父进程为写端,子进程为读端):
1、父进程写的慢,子进程就要阻塞(管道为空,读就要阻塞),等管道有数据才读
2、父进程写的快,管道为满时,父进程要阻塞,子进程要读管道里的数据
3、读端在读,写端关闭,读端把管道数据读完后,read=0说明读到了管道文件末尾
4、读端关闭,OS就会把写端进程给杀掉(避免浪费资源)
三、命名管道
命名管道有自己的路径,跟普通文件不同的是它不做刷新内核缓冲区给磁盘,它的特性与匿名管道几乎差不多,除了两个进程通信是毫无相关的进程,无血缘关系的
实现命名管道的编写
创建命名管道
系统调用函数:mkfifo(char* path,mode)命令创建:mkfifo 文件名
c++创建命名管道:
框架如下:
#defineMODE0666#defineTO_WRITE1#defineTO_READ2classnamed_pipe{public:named_pipe(std::string pfile_path="./fifo"):_pfile_path(pfile_path){}~named_pipe(){}private:std::string _pfile_path;pid_t _fd=-1;};创建管道文件的路径可以随意给我这里就是当前路径
1.创建管道
stat函数是判断你给的这个文件是否已经存在,struct stat结构体是会返回里面的属性,你需要可以通过这个结构体拿到
这里有个坑是不能用open函数用只读的方式打开文件看返回值的,因为对管道文件很特殊如果文件存在它会一直卡在这里,直到有进程以写的方式打开了管道文件才会往后走,所以如果这里用open会有bug
voidBuild_pipe(){if(IF_EXIST()){std::cout<<"命名管道已存在,不需要再次创建!!!"<<std::endl;return;}intn=mkfifo(_pfile_path.c_str(),MODE);if(n<0){std::cerr<<"mkfifo errno:"<<errno<<strerror(errno)<<std::endl;}std::cout<<"命名管道创建成功!!!"<<std::endl;}boolIF_EXIST(){// 能拿到文件的属性structstatst;intn=stat(_pfile_path.c_str(),&st);if(!n)returntrue;elsereturnfalse;}2.让进程拿到不同的权限
voidGet_Power(intmode){if(mode==TO_WRITE){_fd=open(_pfile_path.c_str(),O_WRONLY);if(_fd<0){std::cout<<"open write end failure!!!"<<std::endl;}}elseif(mode==TO_READ){_fd=open(_pfile_path.c_str(),O_RDONLY);if(_fd<0)std::cout<<"open read end failure!!!"<<std::endl;}else{std::cout<<"函数传参错误"<<std::endl;}//下面是测试如果为管道文件,使用open打开文件时,不打开写端,open函数会一直堵塞if(_fd>0){std::cout<<"open over"<<std::endl;}}3.删除管道文件
voidDelete_namedfile(){if(!(IF_EXIST()))return;intn=unlink(_pfile_path.c_str());std::cout<<"删除管道文件成功!!!"<<std::endl;}后面就可以创建两个进程来看了,这里就展示进程server.cpp和client.cpp的代码来模拟客户端向服务端发送消息
#include"named_pipe.hpp"//server.cppintmain(){named_pipe np;np.Build_pipe();np.Get_Power(TO_READ);std::string msg;while(true){charoutbuff[128];ssize_t n=read(np.get_fd(),outbuff,sizeof(outbuff)-1);if(n>0){outbuff[n]='\0';std::cout<<outbuff<<std::endl;}if(n==0){//客户端关闭了,说明我也要结束了std::cout<<"读取客户数据完毕!!!"<<std::endl;break;}else{std::cerr<<"read erro"<<std::endl;}}np.Delete_namedfile();return0;}#include"named_pipe.hpp"//client.cppintmain(){named_pipe np;np.Get_Power(TO_WRITE);while(true){std::string msg;getline(std::cin,msg);ssize_t n=write(np.get_fd(),msg.c_str(),msg.size());if(n>0){std::cout<<"当前客户端向服务端输入"<<n<<"个字节"<<std::endl;}}return0;}