首页 > 代码库 > Linux管道(匿名PIPE)

Linux管道(匿名PIPE)

管道基本概念

    管道是Unix中最古老的进程间通信的形式。

    我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

      如:ps aux | grep httpd | awk ‘{print $2}‘

 

管道示意图



管道的本质

    固定大小的内核缓冲区

管道限制

    1)管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;

    2)匿名管道只能用于具有共同祖先的进程(如父进程与fork出的子进程)之间进行通信;[通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程共享该管道]

 

匿名管道pipe

SYNOPSIS
       #include <unistd.h>
       int pipe(int pipefd[2]);

 

功能

    创建无名管道

参数

   Pipefd:文件描述符数组,其中pipefd[0]表示读端,pipefd[1]表示写端

 

管道创建示意图

//自己实现管道
void err_exit(string str);

int main()
{
    int pipefd[2];
    if (pipe(pipefd) == -1)
        err_exit("pipe error");

    pid_t pid;
    if ((pid = fork()) < 0)
        err_exit("fork error");
    if (pid == 0)   //In Child, Write pipe
    {
        close(pipefd[0]);
        //使得STDOUT_FILENO也指向pipefd[1],亦即ls命令的输出将打印到管道中
        dup2(pipefd[1],STDOUT_FILENO);
        //此时可以关闭管道写端
        close(pipefd[1]);

        execlp("/bin/ls","/bin/ls",NULL);

        //如果进程映像替换失败,则打印下面出错信息
        fprintf(stderr,"Child: execlp error");
        exit(0);
    }

    //In Parent
    close(pipefd[1]);
    //使得STDIN_FILENO也指向pipefd[2],亦即wc命令将从管道中读取输入
    dup2(pipefd[0],STDIN_FILENO);
    //此时可以关闭管道读端
    close(pipefd[0]);
    execlp("/usr/bin/wc","/usr/bin/wc","-w",NULL);

    //如果进程映像替换失败,则打印下面出错信息
    fprintf(stderr,"Parent: execlp error");

    return 0;
}

void err_exit(string str)
{
    perror(str.c_str());
    exit(EXIT_FAILURE);
}

示例:管道编程实践

void err_exit(string str);

int main()
{
    int pipefd[2];
    int ret;
    if ((ret = pipe(pipefd)) != 0)
    {
        err_exit("pipe error");
    }

    pid_t pid = fork();
    if (pid == -1)
    {
        err_exit("fork error");
    }

    if (pid == 0)   //In Child, Write pipe
    {
        close(pipefd[0]);   //Close Read pipe
        string str("I Can Write Pipe from Child!");

        write(pipefd[1],str.c_str(),str.size());    //Write to pipe
        close(pipefd[1]);
        exit(0);
    }

    //In Parent, Read pipe
    close(pipefd[1]);   //Close Write pipe
    char buf[1024];
    memset(buf,0,sizeof(buf));
    read(pipefd[0],buf,sizeof(buf));    //Read from pipe
    cout << "Read from pipe: " << buf << endl;
    close(pipefd[0]);

    return 0;
}

void err_exit(string str)
{
    perror(str.c_str());
    exit(EXIT_FAILURE);
}

匿名管道读写规则

1)管道空时

    O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。

    O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN

 

2)管道满时

    O_NONBLOCK disable: write调用阻塞,直到有进程读走数据

    O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

 

3)管道不停被写,写满

    O_NONBLOCK disable: write调用阻塞(Block)

    O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

//示例:设置父进程Unblock读PIPE
int main()
{
    int pipefd[2];
    int ret;
    if ((ret = pipe(pipefd)) != 0)
    {
        err_exit("pipe error");
    }

    pid_t pid = fork();
    if (pid == -1)
    {
        err_exit("fork error");
    }

    if (pid == 0)   //In Child, Write pipe
    {
        sleep(10);
        close(pipefd[0]);   //Close Read pipe
        string str("I Can Write Pipe from Child!");

        write(pipefd[1],str.c_str(),str.size());    //Write to pipe
        close(pipefd[1]);
        exit(0);
    }

    //In Parent, Read pipe
    close(pipefd[1]);   //Close Write pipe
    char buf[1024];
    memset(buf,0,sizeof(buf));

    //Set Read pipefd UnBlock!
    int flags = fcntl(pipefd[0],F_GETFL);
    flags |= O_NONBLOCK;
    ret = fcntl(pipefd[0],F_SETFL,flags);
    if (ret != 0)
    {
        err_exit("Set UnBlock error");
    }

    int readCount = read(pipefd[0],buf,sizeof(buf));    //Read from pipe
    if (readCount < 0)
    {
        //read立刻返回,不再等待子进程发送数据
        err_exit("read error");
    }
    cout << "Read from pipe: " << buf << endl;
    close(pipefd[0]);

    return 0;
}

 

4)如果所有管道写端对应的文件描述符被关闭,则read返回0

int main()
{
    int pipefd[2];
    int ret;
    if ((ret = pipe(pipefd)) != 0)
    {
        err_exit("pipe error");
    }

    pid_t pid = fork();
    if (pid == -1)
    {
        err_exit("fork error");
    }

    if (pid == 0)   //In Child
    {
        //close all
        close(pipefd[0]);
        close(pipefd[1]);
        exit(0);
    }

    //In Parent
    sleep(1);
    close(pipefd[1]);   //Close Write pipe, Now all pipefd[1] Closed!!!
    char buf[1024];
    memset(buf,0,sizeof(buf));

    int readCount = read(pipefd[0],buf,sizeof(buf));    //Read from pipe
    if (readCount == 0)
    {
        cout << "OK, read 0 byte" << endl;
    }
    close(pipefd[0]);

    return 0;
}

 

5)如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE

void onSignalAction(int signalNumber)
{
    switch(signalNumber)
    {
    case SIGPIPE:
        cout << "receive signal SIGPIPE: " << signalNumber << endl;
        break;
    default:
        cout << "other signal" << endl;
        break;
    }
}

int main()
{
    if (signal(SIGPIPE,onSignalAction) != 0)
    {
        err_exit("signal error");
    }

    int pipefd[2];
    int ret;
    if ((ret = pipe(pipefd)) != 0)
    {
        err_exit("pipe error");
    }

    pid_t pid = fork();
    if (pid == -1)
    {
        err_exit("fork error");
    }

    if (pid == 0)   //In Child, Write pipe
    {
        //Wait Parent Close pipefd[0]
        sleep(1);
        close(pipefd[0]);
        string str("I Can Write Pipe from Child!");

        write(pipefd[1],str.c_str(),str.size());    //Write to pipe
        close(pipefd[1]);
        exit(0);
    }

    //In Parent, Close All Pipe
    close(pipefd[1]);
    close(pipefd[0]);

    wait(NULL);
    return 0;
}

Linux PIPE特征

    1)当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性

    2)当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。

 

//示例:测试PIPE_BUF大小
int main()
{
    int pipefd[2];
    int ret = pipe(pipefd);
    if (ret < 0)
    {
        err_exit("pipe error");
    }

    int flags = fcntl(pipefd[1],F_GETFL);
    flags |= O_NONBLOCK;
    ret = fcntl(pipefd[1],F_SETFL,flags);
    if (ret < 0)
    {
        err_exit("fcntl error");
    }

    //Write test
    unsigned int countForTestPipe = 0;
    while (true)
    {
        ret = write(pipefd[1],"a",1);
        if (ret < 0)
        {
            break;
        }
        ++ countForTestPipe;
    }

    cout << "size = " << countForTestPipe << endl;
}

/**测试结果:Ubuntu 14.04 X64
  xiaofang@xiaofang-Lenovo-G470:~/apue/it$ ./main 
  size = 65536
*/


附-管道容量查询

    man 7 pipe

 

 

 

附-深入理解文件描述符

int main()
{
    close(STDIN_FILENO);
    if (open("readfile.txt",O_RDONLY) == -1)
    {
        err_exit("open read error");
    }
    close(STDOUT_FILENO);
    if (open("writefile.txt",O_WRONLY|O_TRUNC|O_CREAT,0644) == -1)
    {
        err_exit("open write error");
    }

    if (execlp("/bin/cat","/bin/cat",NULL) == -1)
    {
        err_exit("execlp error");
    }

    return 0;
}

Linux管道(匿名PIPE)