目录
匿名管道
管道的创建:
创建子进程:
关闭不需要的fd:
发送消息:
管道的5种特征:
管道的4种情况:
命名管道
创建命名管道:
删除命名管道:
手写命名管道:
完整代码:
我们先来回答下面的几个问题再来正式的进入管道的学习;
1、进程为什么要通信?
进程是需要某种协同的,所有如何协同的前提条件是通信-->数据是有类别的-->通知就绪的、单纯的传递数据的、控制相关信息的...
2、进程如何通信?
a、进程间通信,成本可能会比较高
b、进程间通信前提:先让不同进程看到同一份(操作系统)资源(“一份内存”)
3、进程通信的常用方式
1、system V
2、Posix
我们重点讨论system V方式:有三种
- 消息队列
- 内存共享(重点讨论)
- 信号量
如何直接复原内核代码直接通信呢?
管道:
1、命名管道
2、匿名管道
匿名管道
#include <unistd.h>
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<iostream>
#include<unistd.h>
#include<cerrno>
#include<cstring>
using namespace std;int main()
{//1、创建管道int pipfd[2];int n =pipe(pipfd);//输出型参数,rfd,wrdif(n!=0){cerr<<"errno:"<<errno<<":"<<"errstring:"<<strerror(errno)<<endl;}//管道创建成功cout<<"pipfd[0]:"<<pipfd[0]<<" pipfd[1]"<<pipfd[1]<<endl;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:"<<inbuffer<<endl;}}}
运行结果:
父进程确实接收到了子进程发送的 信息,因此通信完成;
管道的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 <iostream>
#include <string>
#include<cstdio>
#include<cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>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: