工学1号馆

home

初识管道

By Wu Yudong on December 06, 2016

管道是Unix系统IPC的最古老的形式,所有的Unix都支持管道,管道的局限性:

(1)半双工的,某些系统提供全双工,但是为了最佳可移植性,还是认为半双工

(2)只能在具有公共祖先的两个进程之间使用

管道通过调用pipe函数创建:

#include <unistd.h>
init pipe(int fd[2]);

由参数fd返回两个文件描述符:f[0]为读打开,f[1]为写打开,f[1]的输出是f[0]的输入

单个进程中的管道几乎没有任何用处,通常进程会先调用pipe,接着调用fork,从而创建从父进程到子进程的IPC通道,反之亦然

对于fork之后数据流的方向取决于父进程与子进程关闭的文件描述符

当管道的一端被关闭后,有两条规则:

(1)当read一个写端已被关闭的管道时,在所有数据被读取后,read返回0,表示文件结束

(2)当write一个读端已被关闭的管道时,则产生信号SIGPIPE,如果忽略信号或者捕获该信号并从其处理程序返回,则write返回-1,errno设置为EPIPE

在写管道的时候,常量PIPE_BUF规定了内核的管道缓冲区大小

下面一个简单的程序实现了父进程到子进程的管道,父进程经管道向子进程传送数据

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <errno.h>  
#include <signal.h>  
#include <string.h>  
#include <sys/stat.h>   
#include <stropts.h> 

#define    MAXLINE    4096

//输出错误信息并退出      
void err_sys(const char *str)      
{      
    fprintf(stderr, "%s\n", str);      
    exit(1);      
}  
  
int
main(void)
{
    int        n;
    int        fd[2];
    pid_t    pid;
    char    line[MAXLINE];
    char *s = "hello wuyudong!\n";

    if (pipe(fd) < 0)
        err_sys("pipe error");
    if ((pid = fork()) < 0) {
        err_sys("fork error");
    } else if (pid > 0) {        /* 父进程 */
        close(fd[0]);
        write(fd[1], s, strlen(s));
    } else {                    /* 子进程 */
        close(fd[1]);
        n = read(fd[0], line, MAXLINE);
        write(STDOUT_FILENO, line, n);
    }
    exit(0);
}

wu@ubuntu:~/myapue$ gcc usepipe.c -o usepipe
wu@ubuntu:~/myapue$ ./usepipe
wu@ubuntu:~/myapue$ hello wuyudong!

上面实现的是半双工单向的管道,即从父进程写到管道,再到子进程从管道中读,最后再显示

接下来通过增加一个管道来实现一个双向数据流,具体步骤如下:

(1)创建2个管道(fd1和fd2)

(2)fork

(3)父进程关闭fd1[0]

(4)父进程关闭fd2[1]

(5)子进程关闭fd1[1]

(6)父进程关闭fd2[0]

具体代码如下:

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <errno.h>  
#include <signal.h>  
#include <string.h>  
#include <sys/stat.h>   
#include <stropts.h> 

#define    MAXLINE    4096

void client(int readfd, int writefd)
{
    size_t len;
    ssize_t n;
    char buf[MAXLINE];

    fgets(buf, MAXLINE, stdin);  //输入文件路径保存在buf中
    len = strlen(buf);

    if(buf[len-1] == '\n')    //除去换行符
      len--;

    write(writefd, buf, len); //将文件路径写入管道

    while((n = read(readfd, buf, MAXLINE)) > 0)  //从管道中读取n字符到buf
      write(STDOUT_FILENO, buf, n);    //将buf中的n个字符显示终端
}

void server(int readfd, int writefd)
{
    int fd;
    ssize_t n;
    char buf[MAXLINE + 1];

    //read函数:当管道或者FIFO包含的字节少于指定的字节数,read返回可用的字节数
    if((n = read(readfd, buf, MAXLINE)) == 0) {
        printf("read end of file!");
        exit(0);
    }
    buf[n] = '\0';

    if((fd = open(buf, O_RDONLY))< 0) {
        snprintf(buf+n, sizeof(buf)-n,": can't open, %s\n", strerror(errno));
        n = strlen(buf);
        write(writefd, buf, n); //将buf中的n个字节写入管道
    } else {
        while((n = read(fd, buf, MAXLINE)) > 0) 
          write(writefd, buf, n);
        close(fd);
    }
}

int main(int argc, char **argv)
{
    int pipe1[2], pipe2[2];
    pid_t childpid;

    //创建两个管道
    pipe(pipe1);
    pipe(pipe2);

    if((childpid = fork()) == 0) {    //子进程
        close(pipe1[1]);    //关闭pipe1的写
        close(pipe2[0]);    //关闭pipe2的读

        server(pipe1[0], pipe2[1]);
        exit(0);
    }

    close(pipe1[0]);
    close(pipe2[1]);

    client(pipe2[0], pipe1[1]);
    waitpid(childpid, NULL, 0);

    exit(0);
}

编译运行:

wu@ubuntu:~/myapue$ gcc usepipe1.c -o usepipe1
wu@ubuntu:~/myapue$ ./usepipe1
pipetest
hello wuyudong
pipe test pipe
wu@ubuntu:~/myapue$ ./usepipe1
/etc/shadow
/etc/shadow: can't open, Permission denied
wu@ubuntu:~/myapue$ /no/test
bash: /no/test: No such file or directory

如果文章对您有帮助,欢迎点击下方按钮打赏作者

Comments

No comments yet.
To verify that you are human, please fill in "七"(required)