<Linux系统复习>管道

一、本章重点

1、什么是管道?

2、匿名管道

3、用一个进程遥控另一个进程

4、基于匿名管道的进程池

5、命名管道

01 什么是管道?

1、管道本质就是一段由操作系统维护的内核级别的缓冲区,简单点就是一段内存,只不过这段内存是为进程通信而存在的。

2、进程间通信的成本是比较高的,因为进程的独立性,当父子之间要发生写数据时,常常发生写实拷贝,因而需要操作系统专门提供一段特殊的缓冲区,让进程之间通信。

3、为什么需要进程通信?因为常常需要进程之间交互数据,协同完成任务。

4、管道分为两种:匿名管道和命名管道。

02 匿名管道

1、故名思议,就是没有名字的管道,该管道只能用于具有血缘关系的两个进程通信,常用于父子。

2、管道具体在哪?管道那么多,父子如何看待相同的管道?

 这里需要解释的是:子进程会继承父进程的文件描述符表信息,也就是说,父进程打开的文件,子进程默认也打开了。

以下是证明:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 #include<sys/stat.h>
  5 #include<fcntl.h>
  6 #include<stdlib.h>
  7 #include<string.h>
  8 
  9 int main()
 10 {
 11   umask(0);
 12   int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC , 0666);
 13   if(fd<0)
 14   {
 15     perror("open");
 16   }
 17 
 18   if(fork()==0)
 19   {
 20     const char* msg = "haha\n";                                                                                                                                     
 21     write(fd,msg,strlen(msg));
 22     exit(1);
 23   }
 24   return 0;
 25 }

3、匿名管道具有以下特性

①只有血缘关系才能够使用,常见于父子通信。

②已经实现了同步互斥功能

③面向字节流

④生命周期随进程

⑤半双工

4、命令行中常用的 '|' 是匿名管道

证明:

 sleep100 和sleep 200是兄弟关系

5、匿名管道简单实现进程之间交互数据

#include<iostream>
#include<cstring>
#include<errno.h>
#include<cstdlib>
#include<unistd.h>
#include<stdio.h>
using namespace std;

int main()
{
    int pipefd[2] = {0};
    if(pipe(pipefd)!=0)
    {
        cerr<<"pipe: "<<strerror(errno)<<endl;
    }
    int ret = fork();
    if(ret == 0)
    {
        //我们让子进程读,父进程写,因此子进程需要关闭写端。
        close(pipefd[1]);//1代表写端,0代表读端
        char buf[1024] = { 0 };
        while(true)
        {
            ssize_t s = read(pipefd[0],buf,sizeof(char)*1024);
            if(s < 0)
            {
                cerr<<"read error"<<endl;
                exit(1);
            }
            if(s == 0)
            {
                cerr<<"写端关闭"<<endl;
                exit(2);
            }
            if(s > 0)
            {
                buf[s] = '\0';
                cout<<"子进程收到的数据: "<<buf<<endl;
            }
        }
        close(pipefd[0]);
    }
    else if(ret > 0)
    {
        //让父进程写数据,父进程关闭读端
        close(pipefd[0]);
        char msg[1024] = { 0 };
        while(true)
        {
            if(cin>>msg)
            {
                ssize_t s = write(pipefd[1],msg,strlen(msg));
            }
            else
            {
                break;
            }
        }
        close(pipefd[1]);
    }
    else
    {
        cerr<<"fork error"<<endl;
    }
    return 0;
}

5、匿名管道简单实现一个进程控制另一个进程

#include<iostream>
#include<cstring>
#include<errno.h>
#include<cstdlib>
#include<unistd.h>
#include<stdio.h>
#include<vector>

using namespace std;

typedef void(*task)();
vector<task> tasks;

void func1()
{
    cout<<"进程"<<getpid()<<"正在执行处理日志任务"<<endl;
}

void func2()
{
    cout<<"进程"<<getpid()<<"正在执行网络传输任务"<<endl;
}

void func3()
{
    cout<<"进程"<<getpid()<<"正在执行数据库交互任务"<<endl;
}

void func4()
{
    cout<<"进程"<<getpid()<<"正在执行安全检测任务"<<endl;
}

void LoadTask()
{
    tasks.push_back(func1);
    tasks.push_back(func2);
    tasks.push_back(func3);
    tasks.push_back(func4);
}

int main()
{
    LoadTask();
    int pipefd[2] = {0};
    if(pipe(pipefd)!=0)
    {
        cerr<<"pipe: "<<strerror(errno)<<endl;
    }
    int ret = fork();
    if(ret == 0)
    {
        //我们让子进程读,父进程写,因此子进程需要关闭写端。
        close(pipefd[1]);//1代表写端,0代表读端
        int options = 0;
        while(true)
        {
            ssize_t s = read(pipefd[0],&options,sizeof(int));
            if(s == 0)
            {
                cerr<<"写端关闭"<<endl;
                exit(2);
            }

            if(s < 0)
            {
                cerr<<"read error"<<endl;
                exit(1);
            }

            if(s == 4)
            {
                tasks[options-1]();
            }
            else
            {
                cerr<<"bug? s = "<<s<<endl;
            }
        }
        close(pipefd[0]);
    }
    else if(ret > 0)
    {
        //让父进程写数据,父进程关闭读端
        close(pipefd[0]);
        int options = 0;
        while(true)
        {
            if(cin>>options)
            {
                ssize_t s = write(pipefd[1],&options,sizeof(int));
            }
            else
            {
                break;
            }
        }
        close(pipefd[1]);
    }
    else
    {
        cerr<<"fork error"<<endl;
    }
    return 0;
}

5、基于匿名管道的进程池

下面是一个简单的模型

#include<iostream>
#include<cstring>
#include<errno.h>
#include<cstdlib>
#include<unistd.h>
#include<stdio.h>
#include<vector>
#include<ctime>
#include<sys/wait.h>

using namespace std;

typedef void(*task)();
vector<task> tasks;

typedef pair<int,int> ele;
vector<ele> controls;

void func1()
{
    cout<<"进程"<<getpid()<<"正在执行处理日志任务"<<endl;
}

void func2()
{
    cout<<"进程"<<getpid()<<"正在执行网络传输任务"<<endl;
}

void func3()
{
    cout<<"进程"<<getpid()<<"正在执行数据库交互任务"<<endl;
}

void func4()
{
    cout<<"进程"<<getpid()<<"正在执行安全检测任务"<<endl;
}

void LoadTask()
{
    tasks.push_back(func1);
    tasks.push_back(func2);
    tasks.push_back(func3);
    tasks.push_back(func4);
}

int main()
{
    LoadTask();
    int num = 10;
    //创建num个进程
    for(int i = 0 ; i < num ; i++)
    {
        int pipefd[2] = { 0 };
        if(pipe(pipefd)!=0)
        {
            cerr<<"pipe error"<<endl;
        }

        int id = fork();
        if(id == 0)
        {
            //child
            close(pipefd[1]);
            int options = 0;
            while(true)
            {
                ssize_t s = read(pipefd[0],&options,sizeof(int));
                if(s == 0)
                {
                    cerr<<"写端关闭"<<endl;
                    exit(2);
                }

                if(s < 0)
                {   
                    cerr<<"read error"<<endl;
                    exit(1);
                }

                if(s == 4)
                {
                    tasks[options]();
                }
                else
                {
                    cerr<<"bug? s = "<<s<<endl;
                }   
            }
            close(pipefd[0]);
            exit(1);
        }
        else
        {
            close(pipefd[0]);
            ele e(pipefd[1] , id);
            controls.push_back(e);
        }
    }
    //父进程
    while(true)
    {
        sleep(1);
        srand(time(0));
        int select = rand() % num;
        int opt = rand() % tasks.size();
        int fd = controls[select].first;
        int id = controls[select].second;
        write(fd,&opt,sizeof(int));
        cout<<"父进程发了一个任务给"<<id<<endl;
    }

    for(int i = 0;i < num ;i++)
    {
        waitpid(-1,0,0);
    }
    return 0;
}

可以看出父进程在随机的给进程派发任务,不至于只给一个进程派发,导致其他进程饿死的现象,实现了负载均衡。

03 命名管道

一、介绍命名管道

1、顾名思义,就是有名字的管道。

2、为啥有名字,因为它要创建一个磁盘文件,名字指的是磁盘文件的名字。

3、创建磁盘文件不代表是要进行IO的读写,命名管道还是在内存中进行数据交互。

4、匿名管道和命名管道相同点和不同点

相同点:以管道通信的两个进程指向的都是同一个内存文件,并在此基础上建立管道。

不同点:文件描述符获取的方式不同,匿名管道的文件描述符来源于父子继承,而命名管道的文件描述符来源于打开同一个磁盘文件。

图解:

 

二、完成命名管道的client和server编程

我们使用mkfifo函数来创建命名管道,第一个参数是要创建的磁盘管道文件的路径,第二个参数是设置初始权限,需要注意的是初始权限受umask的影响。

返回值:调用成功返回0,否则返回-1。

 

 server.cc

#include "common.h"

int main()
{
    umask(0);
    if(mkfifo(PATH,0666)!=0)
    {
        cerr<<"mkfifo error"<<endl;
        return 1;
    }
    
    int fd = open(PATH,O_RDONLY);
    if(fd < 0)
    {
        cerr<<"open error"<<endl;
        return 2;
    }


#define SIZE 1024
    char buf[1024] = {0};
    while(true)
    {
        ssize_t s = read(fd,buf,sizeof(buf)-1);
        if(s==0)
        {
            cout<<"写端关闭"<<endl;
            unlink(".fifo");
            close(fd);
            exit(0);
        }
        else if(s>0)
        {
            buf[s] = '\0';
            cout<<"client->server: "<<buf<<endl;
        }
        else
        {
            cerr<<"read error"<<endl;
            close(fd);
            exit(3);
        }
    }
    return 0;
}

这里我在写端关闭的时候调用了unlink(),它负责删除管道文件,防止下次运行server的时候创建管道失败,参数是文件名。

 

client.cc

#include "common.h"

int main()
{
    int fd = open(PATH,O_WRONLY | O_APPEND);
    if(fd<0)
    {
        cerr<<"open error"<<endl;
    }

    while(true)
    {
        char msg[1024] = {0};
        cout<<"请输入信息# ";
        fflush(stdout);
        if(fgets(msg,sizeof(char)*1024,stdin)!=nullptr)
        {
            //msg为 xxxx\n\0
            //下面的write我只传了xxxx
            write(fd,msg,strlen(msg)-1);
        }
        else
        {
            exit(0);
        }
    }
    return 0;
}

common.h

#include<iostream>
#include<stdio.h>
#include<cstdlib>
#include<sys/types.h>
#include<sys/stat.h>
#include<cstring>
#include<fcntl.h>
#include<unistd.h>
#define PATH "./.fifo"
using namespace std;

  • 15
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李逢溪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值