Linux管道基础知识

主题:Linux管道知识
概要:进程间通信,管道相关的知识
编辑:新建 20151024
参考资料:
Linux程序设计,第四版,人民邮电出版社

1 什么是管道 ?

当从一个进程连接数据流到另一个进程时,使用术语管道,表述把一个进程的输出通过管道连接到另一个进程的输入。

2 Popen调用

2.1 函数定义

popen和pclose函数是最简单的在两个程序之间传递数据的函数,原型如下:

#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream); 

popen函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者给它传递接收数据。
command:要调用程序的程序名和相应的参数
open_mode:”r”或”w”,”r”是指调用程序读取被调用程序的输出为输入;”w”是指调用程序能向被调用程序写入数据。
pclose:关闭调用程序与被调用程序之间的数据流。

例1:

void Popen1()
{
    FILE * read_fp;
    char buffer[BUFSIZ+1];
    int chars_read;
    memset(buffer,'\0',sizeof(buffer));

    read_fp=popen("uname -a","r");
    if (read_fp!=NULL)
    {
        chars_read=fread(buffer,sizeof(char),BUFSIZ,read_fp);
        if (chars_read>0)
        {
            printf("Out put is:%s \n",buffer);
            printf("Total len is:%d\n",chars_read);
        }
        pclose(read_fp);        
    }
}

popen函数调用一个程序时,会先启动一个shell,然后将command字符串作为参数传给它。这样做一方面,好处是,通过popen能够启动非常复杂的shell命令,不必自己去完成shell扩展。坏处是,对每个popen调用,不仅要启动一个被请求的程序,还要启动一个shell,调用成本略高。

3 PIPE调用

3.1 函数定义

pipe函数提供了通过两个文件描述符在两个程序之间传递数据的功能,函数原型如下:

#include<unistd.h>
int pipe(int filedes[2])

参数为两个整数类型文件描述符组成的数组,该函数在数组中填上两个新的文件描述符后返回值0 ,如果失败返回1并设置errno来表明失败的原因。两个文件描述符以一种特殊的方式连接起来,写到filedes[1]的数据都可以从filedes[0]读出来。数据根据先进先出的顺序进行处理。

例2:
一个简单的写入-读出程序:

void Pipe1()
{
    int date_processed;
    int fife_pipes[2];

    const char * some_data="who are you";
    char buffer[BUFSIZ+1];
    memset(buffer,'\0',sizeof(buffer));
    if (pipe(fife_pipes)==0)
    {
        date_processed=write(fife_pipes[1],some_data,strlen(some_data));
        printf("Wrote %d bytes\n",date_processed);
        date_processed=read(fife_pipes[0],buffer,BUFSIZ);
        printf("Read %d bytes,is :%s \n",date_processed,buffer);
    }
}

例3:
结合fork调用,通过pipe函数能轻易的进行两个进程间的数据传递。

//--通过pipe进行父子程序间通信
void Pipe2()
{
    int date_processed;
    int fife_pipes[2];

    const char * some_data="who are you";
    char buffer[BUFSIZ+1];
    memset(buffer,'\0',sizeof(buffer));

    pid_t fork_result;

    if (pipe(fife_pipes)==0)
    {
        fork_result=fork();

        if (fork_result==-1)
        {
            fprintf(stderr,"fork error");
        }
        else if (fork_result==0)
        {
            date_processed=read(fife_pipes[0],buffer,BUFSIZ);
            printf("Read from [%d],total %d bytes,is :%s \n ",getpid(),date_processed,buffer);
        }
        else
        {
            date_processed=write(fife_pipes[1],some_data,strlen(some_data));
            printf("Wrote to [%d],total %d bytes\n",getpid(),date_processed); 
        }
    }
}

父进程向管道中写数据,而子进程从管道中读取数据,如图所示:
这里写图片描述

例4:
fork函数只能在父子进行间通信,而通过调用exec可进行两个完全不同的程序之间的通信。

写-生产者程序:

void Pipe3()
{
    int date_processed;
    int fife_pipes[2];

    const char * some_data="who are you";
    char buffer[BUFSIZ+1];
    memset(buffer,'\0',sizeof(buffer));

    pid_t fork_result;
    if (pipe(fife_pipes)==0)
    {
        fork_result=fork();

        if (fork_result==-1)
        {
            fprintf(stderr,"fork error");
        }
        else if (fork_result==0)
        {
            sprintf(buffer,"%d",fife_pipes[0]);
            execl("PipeClient","PipeClient",buffer,(char *) 0);         
        }
        else
        {
            date_processed=write(fife_pipes[1],some_data,strlen(some_data));
            printf("Wrote to [%d],total %d bytes\n",getpid(),date_processed); 
        }
    }

}

读-消费者程序 (PipeClient.c)

#include <unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include <string.h>

int main(int argc,char * argv[])
{
    int data_processed;
    char buffer[BUFSIZ+1];
    int file_descriptor;

    memset(buffer,0,sizeof(buffer));
    sscanf(argv[1],"%d",&file_descriptor);

    data_processed=read(file_descriptor,buffer,BUFSIZ);
    printf("%d -read %d bytes :%s/n",getpid(),data_processed,buffer);

    exit(EXIT_SUCCESS);
}

生产者程序子进程中通过execl函数调用了PipeClient进程,把读文件描述符作为参数传递给被调用进程,消费者程序通过传给它的读描述符读取数据。

4 FIFO命名管道

前面的进程通信都由一个共同的进程祖先启动,而想在两个完全不相关的进程间通信还是不很方便,可以用FIFO文件来完成这一功作,通常也被称为命名管道。命名管道是一种特殊类型的文件,在文件系统中以文件名的形式存在,行为与上面没有名字的管道类似。

函数原型:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo( const char *pathname, mode_t mode );

第一个参数(pathname)是将要在文件系统中创建的一个专用文件。第二个参数(mode)用来规定FIFO的读写权限。Mkfifo函数如果调用成功的话,返回值为0;如果调用失败返回值为-1。

例4:
创建一个命名管道

void Fifo1()
{
    int res=mkfifo("/home/book/chapter13/my_fifo",0777);
    if (0==res)
    {
        printf("FIFO created \n");
    }else
    {
        printf("FIFO created error \n");
    }
    exit(EXIT_SUCCESS);
}

mkfifo函数创建一个特殊的文件,文件类型为p,文件模式由参数mode和用户掩码(umask)设置共同确定。

4.1 使用open打开FIFO文件

open函数的原型为:

#include <fcntl.h>
int open(const char *pathname, int oflag, ... );

oflag 用于指定文件的打开/创建模式。

使用O_RDONLY模式:
open(const char *pathname, O_RDONLY );
open调用将阻塞,除非一个进程以写方式打开同一个FIFO,否则它不会返回。
使用O_RDONLY|O_NONBLOCK 模式:
open(const char *pathname, O_RDONLY |O_NONBLOCK );
即使没有其他进程以写方式打开FIFO,这个调用也能成功返回。
使用O_WDONLY模式:
open(const char *pathname, O_WDONLY );
open调用将阻塞,除非一个进程以读方式打开同一个FIFO,否则它不会返回。
使用O_WDONLY|O_NONBLOCK 模式:
open(const char *pathname, O_WDONLY |O_NONBLOCK );
这个函数调用总是立刻返回,但如果没有进程以读方式打开FIFO,open调用将返回一个错误并且FIFO也不会被打开。如果确实有一个进程以读方式打开FIFO,那么可以通过它返回的文件描述符对这个FIFO文件进行写操作。

例5:
创建一个生产者程序,它在需要时创建管道,并尽可能快的向管道中写入数据。

void Fifo3()
{
    int pipe_fd;
    int res;
    int open_mode=O_WRONLY;
    int bytes_sent=0;
    char buffer[BUFSIZ+1];
    int end_tag=65535;

    if(access(FIFO_NAME,F_OK)==-1)
    {
        res=mkfifo(FIFO_NAME,0777);
        if (res!=0)
        {
            fprintf(stderr,"Created fifo error");
            exit(EXIT_FAILURE);
        }
    }
    printf("Prosess [%d] open fifo\n",getpid());
    pipe_fd=open(FIFO_NAME,open_mode);
    printf("Prosess [%d] open fifo result [%d]\n",getpid(),pipe_fd);
    if (pipe_fd!=-1)
    {
        while(bytes_sent<end_tag)
        {
            res=write(pipe_fd,buffer,BUFSIZ);
            if (res==-1)
            {
                fprintf(stderr,"Write error on pipe\n");
                exit(EXIT_FAILURE);
            }
            bytes_sent+=res;
        }
        close(pipe_fd);
    }
    else
    {
        exit(EXIT_FAILURE);
    }

    printf("Prosess [%d] finished\n",getpid());
    exit(EXIT_SUCCESS);
}

创建消费者模式,它从FIFO中读取数据并抛弃它们。

#include <unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include <string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#define FIFO_NAME "/home/book/chapter13/my_fifo"

int main(int argc,char * argv[])
{
    int pipe_fd;
    int res;
    int open_mode=O_RDONLY;
    int bytes_read=0;
    char buffer[BUFSIZ+1];

    memset(buffer,0,sizeof(buffer));

    printf("Prosess [%d] open fifo\n",getpid());
    pipe_fd=open(FIFO_NAME,open_mode);
    printf("Prosess [%d] open fifo result [%d]\n",getpid(),pipe_fd);

    if (pipe_fd!=-1)
    {
        do 
        {
            res=read(pipe_fd,buffer,BUFSIZ);
            bytes_read+=res;
        } while (res>0);        

        close(pipe_fd);
    }
    else
    {
        exit(EXIT_FAILURE);
    }

    printf("Prosess [%d] finished,read [%d] bytes\n",getpid(),bytes_read);
    exit(EXIT_SUCCESS);
}

作为演示,上面的各函数均可以在main函数里直接调用,如:

#include <unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include <string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#define FIFO_NAME "/home/book/chapter13/my_fifo"
int main()
{
    Fifo1();
     return 0;  
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值