Linux管道

管道

管道:**管道本质是内核中的一块缓冲区,多个进程通过访问同一块缓冲区实现通信。**一般用于进程间通信。通过向管道里面读写,完成进程之间的通信。
包括匿名管道和命名管道

匿名管道:为具备血缘关系之间的进程通信
  • a. 管道的4种情况
  •   1.如果管道写端没有数据了,那么读端就必须等待
    
  •   2.管道被写满了,就需要等待读取端读走数据,才能继续写
    
  •   3.如果写端关闭,读端就会一直读取,直到读取到文件末尾.
    
  •   4.如果读端关闭,写入就没有意义,就是异常写入,LINUX会发送信号SIGPIPE(13),终止目标进程
    
  • b. 管道的5种特性
  •   1. 匿名管道,建立一个管道文件,具备亲缘关系的进程可以进行通信
    
  •   2. 匿名管道需要提供同步机制。能读取多少,一般就读取多少。
    
  •   3. 匿名管道是面向字节流的
    
  •   4. 管道也是文件,生命周期随着进程的结束就结束了。就像一直都打开的 ,0,1,2三个默认标准输入输出,进程退出后,会自动释放
    
  •   5. 管道是单向通信的,半双工通信的特殊情况
    

写代码时可能存在的BUG
请添加图片描述

如图,是一个父进程开管道连接到多个子进程。
我们发现,由于子进程会拷贝父进程的文件描述符表,使得后面子进程也有打开对应的文件描述符,使得在关闭的时候出现问题。也就是说,第n个子进程里面,其前面n-1个子进程的写端。原因就是创建子进程时,会拷贝父进程的文件描述符表

linux下创建匿名管道的系统调用
int pipe(int pipefd[2]);

返回值:0成功,-1失败
pipefd[2]:返回一个管道文件描述符数组,pipefd[0]是读端,pipefd[1]是写端。要进行读,就要关闭写端/要进行写,就要关闭读端。

实例代码:

#include <iostream>
#include <string>
#include <unistd.h>
#include <sstream>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>

#define MAX 100
using namespace std;
// a. 管道的4种情况
///  正常情况下:1.如果管道写端没有数据了,那么读端就必须等待
///             2.管道被写满了,就需要等待读取端读走数据,才能继续写
//              3.如果写端关闭,读端就会一直读取,直到读取到文件末尾.
//              4.如果读端关闭,写入就没有意义,就是异常写入,LINUX会发送信号SIGPIPE(13),终止目标进程
// b. 管道的5种特性

// 1. 匿名管道,建立一个管道文件,具备亲缘关系的进程可以进行通信
// 2. 匿名管道需要提供同步机制。能读取多少,一般就读取多少。
// 3. 匿名管道是面向字节流的
// 4. 管道也是文件,生命周期随着进程的结束就结束了。就像一直都打开的 ,0,1,2三个默认标准输入输出,进程退出后,会自动释放
// 5. 管道是单向通信的,半双工通信的特殊情况
int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int pipefd[2] = {0};
    assert(!pipe(pipefd)); //为0退出
    cout << pipefd[0] << "  " << pipefd[1] << endl;
    //创建子进程
    pid_t id = fork();
    if (id < 0)
    {
        cerr << "fork" << endl;
    }

    if (id == 0)
    {
        // //父子间通信
        // //child
        // //关闭不需要的管道进行相应的读或者写操作
        close(pipefd[0]);

        for (int i = 0; i < 10; i++)
        {
            sleep(1);
            stringstream msg;
            msg << "Hello parent process,I am child,pid is" << getpid() << " cnt" << i << endl;
            write(pipefd[1], msg.str().c_str(), msg.str().size());
        }
        cout << "--------child write---------------" << endl;
        sleep(5);
        // close(pipefd[1]);
        // exit(0);

        // pid_t id1 = fork();
        // if(id1 >0) exit(0);

        // //孙子进程
        // //我们发现可以通信,切正好验证了对应的ppid是1,就是一个进程成了孤儿进程之后会被领养
        // close(pipefd[0]);

        // for(int i = 0;i<10;i++)
        // {
        //     stringstream msg;
        //     msg<<"Hello parent process,I am grandson,pid is"<<getpid()<<" cnt"<<i<<",myppid is:"<<getppid()<<endl;
        //     write(pipefd[1],msg.str().c_str(),msg.str().size());
        //     sleep(1);
        // }

        close(pipefd[1]);
        exit(0);
    }

    // Parent
    close(pipefd[1]);
    //关闭写端就可以进行读
    // while (1)
    // {
    //     sleep(1);
    //     char msg[MAX] = {0};
    //     int n = read(pipefd[0], msg, sizeof(msg) - 1);
    //     if (n > 0)
    //     {
    //         msg[n] = 0;
    //         cout << getpid() << ", n=" << n << "child say:" << msg << endl;
    //     }
    //     else
    //     {
    //         break;
    //     }
    // }
    sleep(2);
    close(pipefd[0]);
    sleep(2);
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid == id)
    {
        cout << getpid() << ","
             << "wait success,child exit sig:" << (status & 0x7f) << endl;
    }
    return 0;
}

下面是利用匿名管道实现的一种进程池:其中展现了匿名管道代码编写种可能出现的bug

.h

#pragma once

#include <bits/stdc++.h>
#include <unistd.h>
#include <functional>
#include <ctime>
#include <sys/types.h>
#include <sys/wait.h>
using task_t = std::function<void()>;

void Download()
{
    std::
            cout
        << "I am a task to Download"
        << " hanlder : " << getpid() << std::endl;
}

void Printlog()
{
    std::
            cout
        << "I  am a task to record log"
        << " hanlder : " << getpid() << std::endl;
}

void GetSeeds()
{
    std::
            cout
        << "I  am a task to Get Seeds"
        << " hanlder : " << getpid() << std::endl;
}

class Init
{
    const int g_download_code = 0;
    const int g_PrintLog_code = 1;
    const int g_GetSeeds_code = 2;

public:
    std ::vector<task_t> tasks;
    Init()
    {
        tasks.push_back(Download);
        tasks.push_back(Printlog);
        tasks.push_back(GetSeeds);
    }
    bool CheckSafe(int code)
    {
        if (code >= 0 && code < tasks.size())
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }
    void RunTask(int code)
    {
        return tasks[code]();
    }
    int SelectTask()
    {
        return rand() % tasks.size();
    }
};

Init init;

.cpp

#include "Task_Process_Pool.hpp"

using namespace std;

const int num = 5;
static int CNT = 1;
struct channel
{
    int _ctrlfd;
    pid_t _workerId;
    std::string _name;

public:
    channel(int fd, pid_t id)
        : _ctrlfd(fd), _workerId(id)
    {
        this->_name = "channel-" + to_string(CNT++);
    }
};

void Work()
{
    while (true)
    {
        int code;
        int n = read(0, &code, sizeof(code));
        if (n == 0)
        {

            break;
        }
        else if (n > 0)
        {
            if (!init.CheckSafe(code))
                continue;
            else
            {
                init.RunTask(code);
            }
        }
        else
        {
            cerr << "read error" << endl;
        }
    }
    cout << "my task is ended,pid is " << getpid() << endl;
}

void printDebug(const vector<channel> &c)
{
    for (const auto &channel : c)
    {
        cout << channel._name << "," << channel._ctrlfd << ',' << channel._workerId << endl;
    }
}

void PrintCloseFD(const vector<int> &FDs)
{
    for (auto fd : FDs)
    {
        cout << "process:" << getpid() << "close the FD:" << fd << endl;
    }
}

void CreatChannels(vector<channel> &channels)
{

    //以下代码是由bug的,会导致文件描述符的错乱,导致子进程多余的文件描述符指向
    //导致释放的时候出现问
    // for (int i = 0; i < num; i++)
    // {
    //     int pipfd[2];
    //     int n = pipe(pipfd);
    //     assert(n == 0);
    //     pid_t id = fork();
    //     assert(id != -1);

    //     if (id == 0)
    //     {
    //         // child
    //         close(pipfd[1]);

    //         //这里重定向,能够很方便直接从标准输入里面读取
    //         //可以直接读取0号

    //         dup2(pipfd[0], 0);
    //         Work();
    //         close(pipfd[0]);
    //         exit(0);
    //     }

    //     // father
    //     close(pipfd[0]);
    //     channels.push_back(channel(pipfd[1], id));
    // }

    //为了解决上述问题,写如下代码
    vector<int> temp;
    for (int i = 0; i < num; i++)
    {
        int pipfd[2];
        int n = pipe(pipfd);
        assert(n == 0);
        pid_t id = fork();
        assert(id != -1);

        if (id == 0)
        {
            // child
            if (!temp.empty())
            {
                for (auto fd : temp)
                {
                    close(fd);
                }
                PrintCloseFD(temp);
            }

            close(pipfd[1]);

            //这里重定向,能够很方便直接从标准输入里面读取
            //可以直接读取0号

            dup2(pipfd[0], 0);
            Work();
            close(pipfd[0]);
            exit(0);
        }

        // father
        close(pipfd[0]);
        channels.push_back(channel(pipfd[1], id));
        temp.push_back(pipfd[1]);
    }
}

void SendCommand(const std::vector<channel> &channels, bool Isloop = true, int TaskNum = -1)
{
    int pos = 0;
    while (true)
    {
        int command = init.SelectTask();
        const auto &c = channels[pos++];
        pos %= channels.size();
        // 3.发送任务
        sleep(1);
        cout << "send Command task " << command << " to" << c._name << " Worker is " << c._workerId << endl;
        write(c._ctrlfd, &command, sizeof(command));

        if (!Isloop)
        {
            TaskNum--;
            if (TaskNum <= 0)
            {
                cout << "Send done\n";
                break;
            }
        }
    }
}

void ReleaseChannels(const vector<channel> &channels)
{
    // version2也可以顺着解决这个问题
    for (int i = num - 1; i >= 0; i--)
    {
        close(channels[i]._ctrlfd);
        waitpid(channels[i]._workerId, nullptr, 0);
    }

    //解决由于子进程反复创建,和文件描述符创建的问题,会导致后面的子进程会有写端指向之前的写端
    // version1 ,
    // for (const auto &c : channels)
    // {
    //     close(c._ctrlfd);
    // }
    // for (const auto &channel : channels)
    // {
    //     pid_t rid = waitpid(channel._workerId, nullptr, 0);
    //     if (rid = channel._workerId)
    //     {
    //         cout << "wait child success : " << channel._workerId << endl;
    //     }
    // }
}
int main()
{
    vector<channel> channels;
    CreatChannels(channels);

    //父进程处理任务
    // 1.选择任务
    // 2.选择信道
    SendCommand(channels, false, 2);

    return 0;
}
命名管道

本质:linux下指定对应的文件来作为管道去进行进程间的通信。
系统调用:

    int mkfifo(const char *pathname, mode_t mode);

返回值:成功返回0,失败返回-1
mode:指打开文件的权限,一般来说要减去umask
pathname:打开管道文件的地址

使用实例代码:

.h

#include <bits/stdc++.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
using namespace std;

#define FILENAME "./fifo.tmp"

server

#include "Common.h"
bool MakeFifo()
{
    int n = mkfifo(FILENAME, 0666);
    if (n < 0)
    {
        cerr << "errno:" << errno << ",errstring" << strerror(errno) << endl;
        return 0;
    }
    cout << "mkfifo success..." << endl;
    return 1;
}
int main()
{

    int rfd = open(FILENAME, O_RDONLY);
    if (rfd < 0)
    {
        cerr << "errno" << errno << ",ErrnoString:" << strerror(errno) << endl;
        if (MakeFifo())
        {
            cout << "The last Fifo not exist is solved\n";
            rfd = open(FILENAME, O_RDONLY);
        }
        else
            return 1;
    }
    cout << "open fifo success..." << endl;
    while (1)
    {
        char buffer[1024];
        ssize_t s = read(rfd, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            cout << "Client Say:" << buffer << endl;
            buffer[s] = 0;
        }
        // else if (s == 0)
        // {
        // }
        // else
        // {
        // }
    }
    close(rfd);
    cout << "close fifo success..." << endl;
    return 0;
}

client

#include "Common.h"

int main()
{
    int wfd = open(FILENAME, O_RDWR);
    if (wfd < 0)
    {
        cerr << "errno:" << errno << ",errstring" << strerror(errno) << endl;
        return 1;
    }
    cout<<"open success\n";
    string msg;
    while (1)
    {
        cout << "Please Enter:\n";
        getline(cin, msg);
        ssize_t s = write(wfd, msg.c_str(), msg.size());
        if (s < 0)
        {
            cerr << "errno:" << errno << ",errstring" << strerror(errno) << endl;
            break;
        }
    }
    close(wfd);
    return 0;
}
  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值