1. 程式人生 > >第十章 I/O重定向和管道

第十章 I/O重定向和管道

0.摘要

概念與技巧
-I/O重定向:概念與原因
-標準輸入,輸出和標準錯誤的定義
-重定向標準I/O到檔案
-使用fork來為其他程式重定向
-管道(Pipe)
-建立管道後呼叫fork
相關的系統呼叫與函式
-dup,dup2
-pipe

1.shell程式設計

首先將介紹編寫shell指令碼時的I/O重定向和管道起的作用.然後,本章將介紹作業系統中對I/O重定向的支援.最後,寫一個程式來改變程序的輸入和輸出流..

utmp函式列出使用者列表

2.標準I/O與重定向的若干概念

shell中的三種流分別位如下三種:
1.標準輸入–需要處理的資料流
2.標準輸出–結果資料流
3.標準錯誤輸出–錯誤訊息流

2.1 3個標準檔案描述符

stdin:0
stdout:1
stderr:2

2.2預設的連線:tty

在unix和linux中,shell預設連結著stdin,stdout,stderr三個檔案。很多程式沒有輸入,只是把正確的輸出寫到檔案描述符位1,並且把錯誤訊息寫到檔案描述符2中.

2.3重定向I/O的是shell來操作而不是程式

當使用輸出重定向標誌,命令cmd>filename是由shell將檔案描述符1定位到檔案.把原有的連結打斷.
本章的三個主要的工作:
1.who>userlist 將stdout連線到一個檔案
2.sort

3.如何將stdin定向到檔案

程序是從檔案描述符讀取資料的.並且由將標準輸入重定向到可以從檔案讀入資料.

3.1.方法1:close then open

開始的時候,0連線輸入,1連線輸出,2連接出錯.三種標準流是被連線到終端裝置上的.通過檔案close可以指定標準的輸入輸出關閉。如果呼叫close來關閉程式的stdin(0),之後開啟open一個指定檔案,則它會按照最低可用檔案描述符來匹配,得到開啟新檔案對應到stdin位置

close(0);
fd = open(<file path>,O_RDONLY);

3.2.方法2:open..close..dup..close

執行過程
1.open開啟一個檔案,fd不為0,
2.close(0)關閉檔案描述符為0。
3.dup(fd)來將檔案描述符fd做一個複製。把複製得到的檔案fd根據最低可用檔案描述符原則與0相連。
4.close(fd)關閉最先開啟的檔案。
其中在這個方法中,close(0)和dup(fd)可以結合在一起作為一個單獨的系統呼叫dup2

fd=open(<file path>)
close(0)
dup(fd)
close(fd)

3.3.方法3:open..dup2..close

#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
//#define DUP2
#define CLOSE_DUP

void main()
{
    int fd;
    int newfd;  //the new file descriptor
    char line[100];
    fgets(line,100,stdin);
    printf("%s",line);

    fd = open("./data",O_RDONLY); //read data from file which creates by myself
    #ifdef CLOSE_DUP
        close(0);
        dup(fd);
    #else
        newfd = dup2(fd,0);
    #endif
    if(newfd !=0)
    {
        perror("gouge cuole");
        exit(1);
    }
    close(fd);  //every time you wouldn't use the file descriptor, you can close it 
    fgets(line,100,stdin);
    printf("%s",line);
}

4.為其他程式重定向I/O:who>userlist

虛擬碼:

pid=fork()
if(pif ==0 )
{
    close(1);
    fd=creat(<file>,0644);//open a newho file
    execlp("who","who",NULL);
    perror("execlp");
    exit(1);
}
if(pid!=0)
{
    wait(NULL);
}

重定向到檔案的小結:
1.標準輸入、輸出以及錯誤輸出分別對應於檔案描述符0、1、 2
2.核心總是使用最低可用檔案描述符
3.檔案描述符集合通過exec呼叫傳遞,且不會被改變。

5.管道程式設計

管道是核心中的一個單向的資料通道。管道有一個讀取端和一個寫入端。實現who|sort的兩個技巧:如何建立管道,如何使標準輸入和輸出通過管道聯絡起來。

5.1建立管道

result = pipe(int array[2]);其中管道的輸出端是array[0]中的檔案描述符表示,輸入端是array[1]中的檔案描述符表示.

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
void main()
{
    int len,i,apipe[2]; /*two file descriptor*/
    char buf[BUFSIZ];

    if(pipe(apipe)==-1)
    {   
        perror("could not make pipe");
        exit(1);
    }   
    printf("Got a pipe! It is file descriptors: {%d %d \n",apipe[0],apipe[1]);
    while(fgets(buf,BUFSIZ,stdin))
    {   
        len = strlen(buf);
        if(write(apipe[1],buf,len)!=len)
        {
            perror("writing to pipe");
            break;
        }

        for(i=0;i<len;i++)  /*wipe*/
            buf[i]='X';         //nothing will be changed
        if((len = read(apipe[0],buf,BUFSIZ))==-1)
        {
            perror("reading from price");
            break;
        }
        if(write(1,buf,len)!=len){ /*send*/
            perror("writing to stdout"); /*to*/
            break;                      /*pipe*/
        }
    }   
}

可以看到上述程式碼建立一個管道,預設使用3和4檔案描述符.執行過程如圖
這裡寫圖片描述
當然很少有自己拿個管道和自己玩的,一般用在程序間通訊的比較多,要把pipe和fork結合起來玩.

5.2使用fork來共享管道

父程序建立子程序後,他們之間共享管道,兩個程序都訪問管道的兩端。而程序建立時複製了檔案描述符表等其他內容。因此管道可以用作程序間的資料交流
這裡寫圖片描述

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#define CHILD_MESS "I want a cookie\n"
#define PARENTS_MESS "I am the parents\n"
#define opps(m,x)  {perror(m);exit(x);}

void main()
{
    int apipe[2];
    int len;
    int read_len;
    char buf[BUFSIZ];
    if(pipe(apipe)==-1)
    {   
        opps("pipe",1);
    }   

    switch(fork())
    {   
        case -1: 
            opps("fork ",1);
        case 0:
            len = strlen(CHILD_MESS);

            while(1){
                if(write(apipe[1],CHILD_MESS,len)!=len) //write the CHILD_MESS to parents
                    opps("child wirte",3);
                sleep(5);
            }
            /*parent reads from pipe and write to pipe*/
        default:
            len = strlen(PARENTS_MESS);
            while(1){
                if(write(apipe[1],PARENTS_MESS,len)!=len) //write the PARENTS_MESS to apipe[0]
                    opps("parents write",4);
                sleep(1);
                read_len = read(apipe[0],buf,BUFSIZ);
                if(read_len <=0)
                    break;
               write(1,buf,read_len);
            }
    }
}

當然這個管道存在大家都可以往裡面寫,可以在裡面讀,所以就容易造成混亂.所以要讓子程序寫,讓父程序讀,在不同的程序中關閉無用的檔案描述符.

5.3技術細節:管道並非檔案

管道像檔案一樣,是一種不帶有任何結構的位元組序列,另一方面,管道又與檔案不同.
1.從管道中讀資料
(1)管道讀取 阻塞
當程序試圖從管道中讀取資料時,程序被掛起直到讀取完成,隨之而來的如何避免這個問題?(flush寫完成之後flush一下?)
(2)管道的讀取結束標誌
當所有寫者關閉了管道的寫資料端時,試圖從管道讀取資料的呼叫返回0,意味著檔案的結束
(3)多個讀者可能引起麻煩
管道是一個佇列。當程序從管道中讀取資料之後,資料已經不存在了。所以多個讀者可能會出現讀取資料不完整的情況.
2.向管道中寫資料
(1)寫入資料阻塞直到管道有空間去接納新的資料
如果一次寫入量超過管道大小,則等管道傳輸完前一部分,再匯入一部分。
(2)寫入必須保證一個最小的塊大小:POSIX保證核心不會被拆分成小於512位元組的塊,而linux保證的是4096位元組連續.
(3 )若無讀者在讀取資料,則寫操作執行失敗
如果所有的讀者關閉了管道的埠,那麼首先,核心傳送SIGPIPE訊息給程序。若程序被終止,則無任何事情發橫。否則,write返回-1報錯,errno設定位EPIPE

以上已經介紹了了如何在一個程序中輸入,輸出資料.那麼資料是怎麼在兩個程序直接傳遞,切是雙向傳遞呢?還有上述說的管道只是在父子程序之間能夠起到傳遞資料的作用,且是單項傳遞的,如何做到雙向傳遞?如何在兩個距離很遠的計算機之間傳遞資訊?如何做到可靠傳輸?