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

目录

匿名管道

管道的创建:

创建子进程:

关闭不需要的fd:

发送消息:

管道的5种特征:

管道的4种情况:

命名管道

创建命名管道:

删除命名管道:

手写命名管道:

完整代码:


我们先来回答下面的几个问题再来正式的进入管道的学习;

 1、进程为什么要通信?

进程是需要某种协同的,所有如何协同的前提条件是通信-->数据是有类别的-->通知就绪的、单纯的传递数据的、控制相关信息的...

2、进程如何通信?

a、进程间通信,成本可能会比较高

b、进程间通信前提:先让不同进程看到同一份(操作系统)资源(“一份内存”)

3、进程通信的常用方式

1、system V

2、Posix

我们重点讨论system V方式:有三种

  1. 消息队列
  2. 内存共享(重点讨论)
  3. 信号量

如何直接复原内核代码直接通信呢?

管道:

        1、命名管道

        2、匿名管道

匿名管道

#include <unistd.h>

int pipe(int pipefd[2]);

pipefd[2]:文件描述符数组,pipefd[0]:readpipefd[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,wrd
    if(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种特征:

  1. 匿名管道:只用来直接进行具有血缘关系的进程之间,进程通信,常用父子进程之间通信
  2. 管道内部,自带进程之间的同步机制--------->多执行流文件的时候,具有明显的顺序性!
  3. 管道文件的生命周期是随进程的
  4. 管道文件在通信的时候,是面向字节流的  ----->w次数和r次数是不匹配的
  5. 管道的通信模式是一种特殊的半双工模式

管道的4种情况:

  1. 如果管道内部是空的&&write fd 没有关闭,读取条件不具备,读进程会被阻塞 --->wait --->读取条件具备 --->写入数据
  2. 管道被写满&&read fd 不读且没有关闭,管道被写满,写进程会被阻塞 --->写条件不具备-->写条件具备 ----->读取数据
  3. 管道一直在读 && 写段关闭wfd,读端read返回值会读到0,表示读到了文件结尾
  4. 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:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值