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;  
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章