【Linux】进程间通信---匿名管道、命名管道(超详解)

【Linux】进程间通信---匿名管道、命名管道(超详解)

目录

匿名管道

管道的创建:

创建子进程:

关闭不需要的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:

相关尊享内容