程序間通訊 方式三 無名管道

2021-08-22 00:04:48 字數 4080 閱讀 3967

一、什麼是管道

如果你使用過linux的命令,那麼對於管道這個名詞你一定不會感覺到陌生,因為我們通常通過符號「|"來使用管道,但是管理的真正定義是什麼呢?管道是乙個程序連線資料流到另乙個程序的通道,它通常是用作把乙個程序的輸出通過管道連線到另乙個程序的輸入。

舉個例子,在shell中輸入命令:ls -l | grep string,我們知道ls命令(其實也是乙個程序)會把當前目錄中的檔案都列出來,但是它不會直接輸出,而是把本來要輸出到螢幕上的資料通過管道輸出到grep這個程序中,作為grep這個程序的輸入,然後這個程序對輸入的資訊進行篩選,把存在string的資訊的字串(以行為單位)列印在螢幕上。

二、使用popen函式

1、popen函式和pclose函式介紹

有靜就有動,有開就有關,與此相同,與popen函式相對應的函式是pclose函式,它們的原型如下:

#include file* popen (const char *command, const char *open_mode);

int pclose(file *stream_to_close);

poen函式允許乙個程式將另乙個程式作為新程序來啟動,並可以傳遞資料給它或者通過它接收資料。command是要執行的程式名和相應的引數。open_mode只能是"r(唯讀)"和"w(只寫)"的其中之一。注意,popen函式的返回值是乙個file型別的指標,而linux把一切都視為檔案,也就是說我們可以使用stdio i/o庫中的檔案處理函式來對其進行操作。

如果open_mode是"r",主呼叫程式就可以使用被呼叫程式的輸出,通過函式返回的file指標,就可以能過stdio函式(如fread)來讀取程式的輸出;如果open_mode是"w",主呼叫程式就可以向被呼叫程式傳送資料,即通過stdio函式(如fwrite)向被呼叫程式寫資料,而被呼叫程式就可以在自己的標準輸入中讀取這些資料。

pclose函式用於關閉由popen建立出的關聯檔案流。pclose只在popen啟動的程序結束後才返回,如果呼叫pclose時被呼叫程序仍在執行,pclose呼叫將等待該程序結束。它返回關閉的檔案流所在程序的退出碼。

2、例子

很多時候,我們根本就不知道輸出資料的長度,為了避免定義乙個非常大的陣列作為緩衝區,我們可以以塊的方式來傳送資料,一次讀取乙個塊的資料並傳送乙個塊的資料,直到把所有的資料都傳送完。下面的例子就是採用這種方式的資料讀取和傳送方式。原始檔名為popen.c,**如下:

#include #include #include #include int main()

//關閉檔案流

pclose(read_fp);

pclose(write_fp);

exit(exit_success);

} exit(exit_failure);

}

3、popen的實現方式及優缺點

當請求popen呼叫執行乙個程式時,它首先啟動shell,即系統中的sh命令,然後將command字串作為乙個引數傳遞給它。

這樣就帶來了乙個優點和乙個缺點。優點是:在linux中所有的引數擴充套件都是由shell來完成的。所以在啟動程式(command中的命令程式)之前先啟動shell來分析命令字串,也就可以使各種shell擴充套件(如萬用字元)在程式啟動之前就全部完成,這樣我們就可以通過popen啟動非常複雜的shell命令。

而它的缺點就是:對於每個popen呼叫,不僅要啟動乙個被請求的程式,還要啟動乙個shell,即每乙個popen呼叫將啟動兩個程序,從效率和資源的角度看,popen函式的呼叫比正常方式要慢一些。

三、pipe呼叫

如果說popen是乙個高階的函式,pipe則是乙個底層的呼叫。與popen函式不同的是,它在兩個程序之間傳遞資料不需要啟動乙個shell來解釋請求命令,同時它還提供對讀寫資料的更多的控制。

#include int pipe(int file_descriptor[2]);
我們可以看到pipe函式的定義非常特別,該函式在陣列中牆上兩個新的檔案描述符後返回0,如果返回返回-1,並設定errno來說明失敗原因。

陣列中的兩個檔案描述符以一種特殊的方式連線起來,資料基於先進先出的原則,寫到file_descriptor[1]的所有資料都可以從file_descriptor[0]讀回來。由於資料基於先進先出的原則,所以讀取的資料和寫入的資料是一致的。

1、從函式的原型我們可以看到,它跟popen函式的乙個重大區別是,popen函式是基於檔案流(file)工作的,而pipe是基於檔案描述符工作的,所以在使用pipe後,資料必須要用底層的read和write呼叫來讀取和傳送。

2、不要用file_descriptor[0]寫資料,也不要用file_descriptor[1]讀資料,其行為未定義的,但在有些系統上可能會返回-1表示呼叫失敗。資料只能從file_descriptor[0]中讀取,資料也只能寫入到file_descriptor[1],不能倒過來。

例子:首先,我們在原先的程序中建立乙個管道,然後再呼叫fork建立乙個新的程序,最後通過管道在兩個程序之間傳遞資料。原始檔名為pipe.c,**如下:

#include #include #include #include int main()

if(pid == 0)

else

}exit(exit_failure);

}

執行結果為:

可見,子程序讀取了父程序寫到filedes[1]中的資料,如果在父程序中沒有sleep語句,父程序可能在子程序結束前結束,這樣你可能將看到兩個輸入之間有乙個命令提示符分隔。

四、把管道用作標準輸入和標準輸出

下面來介紹一種用管道來連線兩個程序的更簡潔方法,我們可以把檔案描述符設定為乙個已知值,一般是標準輸入0或標準輸出1。這樣做最大的好處是可以呼叫標準程式,即那些不需要以檔案描述符為引數的程式。

為了完成這個工作,我們還需要兩個函式的輔助,它們分別是dup函式或dup2函式,它們的原型如下

#include int dup(int file_descriptor);

int dup2(int file_descriptor_one, int file_descriptor_two);

dup呼叫建立乙個新的檔案描述符與作為它的引數的那個已有檔案描述符指向同乙個檔案或管道。對於dup函式而言,新的檔案描述總是取最小的可用值。而dup2所建立的新檔案描述符或者與int file_descriptor_two相同,或者是第乙個大於該引數的可用值。所以當我們首先關閉檔案描述符0後呼叫dup,那麼新的檔案描述符將是數字0.

例子在下面的例子中,首先開啟管道,然後fork乙個子程序,然後在子程序中,使標準輸入指向讀管道,然後關閉子程序中的讀管道和寫管道,只留下標準輸入,最後呼叫execlp函式來啟動乙個新的程序od,但是od並不知道它的資料**是管道還是終端。父程序則相對簡單,它首先關閉讀管道,然後在寫管道中寫入資料,再關閉寫管道就完成了它的任務。原始檔為pipe2.c,**如下:

#include #include #include #include int main()

if(pid == 0)

else

}exit(exit_success);

}

執行結果為:

從執行結果中可以看出od程序正確地完成了它的任務,與在shell中直接輸入od -c和123的效果一樣。

五、關於管道關閉後的讀操作的討論

現在有這樣乙個問題,假如父程序向管道file_pipe[1]寫資料,而子程序在管道file_pipe[0]中讀取資料,當父程序沒有向file_pipe[1]寫資料時,子程序則沒有資料可讀,則子程序會發生什麼呢?再者父程序把file_pipe[1]關閉了,子程序又會有什麼反應呢?

當寫資料的管道沒有關閉,而又沒有資料可讀時,read呼叫通常會阻塞,但是當寫資料的管道關閉時,read呼叫將會返回0而不是阻塞。注意,這與讀取乙個無效的檔案描述符不同,read乙個無效的檔案描述符返回-1。

六、匿名管道的缺陷

程序間通訊 無名管道

在上次的部落格中,我給大家介紹了程序間通訊的方式 有名管道。管道分為有名管道和無名管道,那麼此次我將給大家介紹一下另一種管道通訊 無名管道。有名管道是可以應用於任何兩個程序之間資料的單向傳遞,而無名管道是相對於有名管道的,無名管道在使用時產生,不使用後釋放,並不會在系統上留下任何痕跡。無名管道因其使...

程序間通訊 無名管道

1.無名管道 pipe 2.有名管道 fifo 3.訊號 signal 4.共享記憶體 share memory 5.訊息佇列 message queue 6.訊號燈集 semaphore set 7.套接字 1.只能用於具有親緣關係的程序之間的通訊 父子或兄弟程序 2.彈弓的通訊模式,具有固定的讀...

程序間通訊 有名管道 無名管道

顧名思義,管道就像是將資料放入到乙個長長的管子中一樣,肯定會有一端寫入資料,稱為寫端,有一段讀出資料,稱為讀端。既然是說像管子一樣那麼它肯定有大小吧,資源不是無窮無盡的,預設下管道的大小是64k,用ulimit a 可以檢視。1 無名管道 管道是半雙工的,資料只能向乙個方向流動 需要雙方通訊時,需要...