目录
匿名管道
管道的创建:
创建子进程:
关闭不需要的fd:
发送消息:
管道的5种特征:
管道的4种情况:
命名管道
创建命名管道:
删除命名管道:
手写命名管道:
完整代码:
我们先来回答下面的几个问题再来正式的进入管道的学习;
1、进程为什么要通信?
进程是需要某种协同的,所有如何协同的前提条件是通信-->数据是有类别的-->通知就绪的、单纯的传递数据的、控制相关信息的...
2、进程如何通信?
a、进程间通信,成本可能会比较高
b、进程间通信前提:先让不同进程看到同一份(操作系统)资源(“一份内存”)
3、进程通信的常用方式
1、system V
2、Posix
我们重点讨论system V方式:有三种
消息队列内存共享(重点讨论)信号量
如何直接复原内核代码直接通信呢?
管道:
1、命名管道
2、匿名管道
匿名管道
#include
int pipe(int pipefd[2]);
pipefd[2]:文件描述符数组,pipefd[0]:read,pipefd[1]:write;
返回值:管道创建成功返回0,创建失败返回错误代码
巧记方法:
0--->嘴巴--->r
1--->钢笔--->w
匿名管道之所以叫匿名管道,是因为它不需要文件名和文件路径;
一个进程打开一个文件时,会以读方式打开和写方式两种方式分别打开;也就是会形成两个file文件;但是第二次打开同一个文件的时候,文件的inode,操作方法集合,缓冲区不会再加载一次了;会指向第一次打开文件时候加载的地址;
为什么父子进程会指向同一个显示器终端打印文件?
子进程会指向父进程的文件描述符表,进而会指向同一个文件;
进程默认会打开三个标准输出:0、1、2,怎么做到的呢?
所有的进程都是bash的子进程,bash打开了,所有的子进程也就默认打开了;
close();为什么子进程主动close(0/1/2);不影响父进程继续使用显示器文件呢?
在file中有一个引用计数ref_count,子进程关闭时,ref_count--;只有ref_count为0时,才会释放文件资源;(注意:这里的引用计数和硬链接里的引用计数不同,但是原理是类似的)
为什么父进程读写后在fork?(看图中的1,2)
本质是为例让父子进程看到同一份资源;
管道的创建:
#include
#include
#include
#include
using namespace std;
int main()
{
//1、创建管道
int pipfd[2];
int n =pipe(pipfd);//输出型参数,rfd,wrd
if(n!=0)
{
cerr<<"errno:"< } //管道创建成功 cout<<"pipfd[0]:"< return 0; } 运行结果: 通过观察运行结果,我们可以证明文件被打开了两次; 创建子进程: pid_t id=fork(); 关闭不需要的fd: 如果是父进程读取,子进程写入的话;父进程就要close(1);子进程close(0); 如果是父进程写入,子进程读取的话;父进程就要close(0);子进程close(1); 最后要记得添加一个waitpid,使整个代码完整; 发送消息: 怎么发送呢? 管道也是文件,所以发送信息可以直接用系统的read/write; void childwrite(int wfd) { string message="child process";//发送给父进程的信息 while(true) { write(wfd,message.c_str(),message.size());//写入管道时,没有写入\0,没有必要写入; sleep(1); } } void father_read(int rfd) { char inbuffer[size]; while(true) { ssize_t n=read(rfd,inbuffer,sizeof(inbuffer)-1); if(n>0) { inbuffer[n]=0; cout<<"father get message:"< } } } 运行结果: 父进程确实接收到了子进程发送的 信息,因此通信完成; 管道的5种特征: 匿名管道:只用来直接进行具有血缘关系的进程之间,进程通信,常用父子进程之间通信管道内部,自带进程之间的同步机制--------->多执行流文件的时候,具有明显的顺序性!管道文件的生命周期是随进程的管道文件在通信的时候,是面向字节流的 ----->w次数和r次数是不匹配的管道的通信模式是一种特殊的半双工模式 管道的4种情况: 如果管道内部是空的&&write fd 没有关闭,读取条件不具备,读进程会被阻塞 --->wait --->读取条件具备 --->写入数据管道被写满&&read fd 不读且没有关闭,管道被写满,写进程会被阻塞 --->写条件不具备-->写条件具备 ----->读取数据管道一直在读 && 写段关闭wfd,读端read返回值会读到0,表示读到了文件结尾rid直接关闭,写端wfd一直进行写入?-->写端进程会被操作系统直接使用13信号关掉,相当于进程出现了异常 命名管道 对于命名管道的原理可以看下面的图,图中很详细的表明了命名管道的内核结构; 创建命名管道: mkfifo 管道名 我们可以通过向管道中写入一下内容,并查看是否可以读取,来检查管道是否创建成功; 注意:我们查看管道文件的大小是会发现管道文件依旧是0; 删除命名管道: unlink 管道名 手写命名管道: 用的是mkfifo系统调用; 第一个参数是路径,第二个参数是权限;(可以回忆一下umask) 运行一下来验证管道是否创建成功: (2)管道创建成功后,我们就要打开这个管道文件; (3)打开文件后,我们就要开始写操作和读操作; 这里我们的server进程读操作,client进程写操作; 运行一下,来检验: 注意一下: 这里我们的读操作不能像下面一下来写,不然会报段错误: 报错的原因是:sizeof(out); sizeof(out)其实是指针本身的大小,不是字符串数据的大小; 完整代码: 这只是.hpp文件的代码; #pragma once #include #include #include #include #include #include #include #include using namespace std; const string comm_path = "./fifo"; #define gCreater 1 #define gUser 2 #define Read O_RDONLY #define Write O_WRONLY #define FD -1 #define SIZE 4069 class namepipe { private: bool openpipe(int flag) { _fd = open(_path.c_str(), flag); if (_fd < 0) { return false; } return true; } public: namepipe(const string &path, int who) : _path(path), _id(who),_fd(FD) { if (_id == gCreater) { int res = mkfifo(path.c_str(), 0666); cout << "namepipe create sucess" << endl; } } ~namepipe() { if (_id == gCreater) { int res = unlink(_path.c_str()); if (res == 0) { cout << "namepipe remove sucess" << endl; } if(_fd!=FD) { close(_fd); } } } bool open_readpipe() { return openpipe(Read); } bool open_writepipe() { return openpipe(Write); } int readpipe(string * out) { char buff[SIZE]; int n=read(_fd,buff,sizeof(buff)); if(n>0) { buff[n]=0; *out=buff; } return n; } int writepipe(const string & in) { write(_fd,in.c_str(),sizeof(in)); } private: const string _path; int _id; int _fd; }; 客户端代码比较简单,这里我就直接放图片了: server.cpp: client.cpp: