管道編程


管道是一個允許單向信息傳遞的通信設備。從管道“寫入端”寫入的數據可以從“讀取
端”讀回。管道是一個串行設備;從管道中讀取的數據總保持它們被寫入時的順序。一般來
說,管道通常用於一個進程中兩個線程之間的通信,或用於父子進程之間的通信。
在shell 中,| 符號用於創建一個管道。例如,下面的程序會使 shell 創建兩個子進程,
一個運行ls而一個運行less:
% ls | less
Shell同時還會創建一個管道,將運行 ls的子進程的標准輸出連接到運行less 的子進
程的標准輸入。由ls輸出的文件名將被按照與發送到終端時完全相同的順序發送到less
的標准輸入。
管道的數據容量是有限的。如果寫入的進程寫入數據的速度比讀取進程消耗數據的速

更快,且管道無法容納更多數據的時候,寫入端的進程將被阻塞,直到管道中出現更多的空
間為止。換言之,管道可以自動同步兩個進程。

5.4.1 創建管道
要創建一個管道,請調用 pipe 命令。提供一個包含兩個 int 值的數組作為參數Pipe
命令會將讀取端文件描述符保存在數組的第0 個元素而將寫入端文件描述符保存在第 1 個
元素中。舉個例子,考慮如下代碼:
int pipe_fds[2];
int read_fd;
int write_fd;

pipe (pipe_fds);
read_fd = pipe_fds[0];
write_fd = pipe_fds[1];

對文件描述符write_fd 寫入的數據可以從read_fd中讀回。
5.4.2 父子進程之間的通信
通過調用pipe 得到的文件描述符只在調用進程及子進程中有效。一個進程中的文件描
述符不能傳遞給另一個無關進程;不過,當這個進程調用 fork 的時候,文件描述符將復制
給新創建的子進程。因此,管道只能用於連接相關的進程。
列表5.7中的程序中,fork 產生了一個子進程。子進程繼承了指向管道的文件描述符。
父進程向管道寫入一個字符串,然后子進程將字符串讀出。實例程序將文件描述符利用
fdopen 函數轉換為FILE *流。因為我們使用文件流而不是文件描述符,我們可以使用包括
printf 和scanf 在內的標准C 庫提供的高級 I/O 函數。

代碼列表 5.7 (pipe.c)通過管道與子進程通信

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

/* 將 COUNT 份 MESSAGE 的副本寫入 STREAM,每次寫入之后暫停 1 秒鍾 */

void writer (const char* message, int count, FILE* stream)
{
for (; count > 0; --count) {
/* 將消息寫入流,然后立刻發送 */
fprintf (stream, "%s\n", message);
fflush (stream);
/* 休息,休息一會兒 */
sleep (1);

}

}

/* 從流中讀取盡可能多的隨機字符串 */

void reader (FILE* stream)
{
char buffer[1024];
/* 從流中讀取直到流結束。 fgets 會不斷讀取直到遇見換行或文件結束符。 */
while (!feof (stream)
&& !ferror (stream)
&& fgets (buffer, sizeof (buffer), stream) != NULL)
fputs (buffer, stdout);
}

int main ()
{
int fds[2];
pid_t pid;

/* 創建一個管道。代表管道兩端的文件描述符將被放置在 fds 中。*/
pipe (fds);
/* 創建子進程。*/
pid = fork ();
if (pid == (pid_t) 0) {
FILE* stream;
/* 這里是子進程。關閉我們得到的寫入端文件描述符副本。*/
close (fds[1]);
/* 將讀取端文件描述符轉換為一個 FILE 對象然后從中讀取消息 */
stream = fdopen (fds[0], "r");
reader (stream);
close (fds[0]);
}
clse {
/* 這是父進程。*/
FILE* stream;
/* 關閉我們的讀取端文件描述符副本。*/
close (fds[0]);
/* 將寫入端文件描述符轉換為一個 FILE 對象然后寫入數據。*/
stream = fdopen (fds[1], "w");
writer ("Hello, world.", 5, stream);
close (fds[1]);
}

return 0;

}

在 main 函數開始的時候,fds 被聲明為一個包含兩個整型變量的數組。對 pipe 的調
用創建了一個管道,並將讀寫兩個文件描述符存放在這個數組中。程序隨后創建一個子進程。
在關閉了管道的讀取端之后,父進程開始向管道寫入字符串。而在關閉了管道的寫入端之后,
子進程開始從管道讀取字符串。
注意,在writer 函數中,父進程在每次寫入操作之后通過調用fflush 刷新管道內容。
否則,字符串可能不會立刻被通過管道發送出去。
當你調用ls | less 這個命令的時候會出現兩次fork 過程:一次為ls創建子進程,
一次為less 創建子進程。兩個進程都繼承了這些指向管道的文件描述符,因此它們可以通
過管道進行通信。如果希望不相關的進程互相通信,應該用FIFO 代替管道。FIFO 將在5.4.5
節“FIFO”中進行介紹。
5.4.3 重定向標准輸入、輸出和錯誤流
你可能經常希望創建一個子進程,並將一個管道的一端設置為它的標准輸入或輸出。利
dup2 系統調用你可以使一個文件描述符等效於另外一個。例如,下面的命令可以將一個
進程的標准輸入重定向到文件描述符fd:
dup2 (fd, STDIN_FILENO);
符號常量STDIN_FILENO 代表指向標准輸入的文件描述符。它的值為 0。這個函數會
關閉標准輸入,然后將它作為fd的副本重新打開,從而使兩個標識符可以互換使用。
相互等效的文件描述符之間共享文件訪問位置和相同的一組文件狀態標志。因此,從
fd中讀取的字符不會再次從標志輸入中被讀取。
列表5.8 中的程序利用dup2 系統調用將一個管道的輸出發送到sort命令當創建了

一個管道之后,程序生成了子進程。父進程向管道中寫入一些字符串,而子進程利用dup2
將管道的讀取端描述符復制到自己的標准輸入,然后執行sort程序。

代碼列表 5.8 (dup2.c )用 dup2 重定向管道輸出

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main ()
{
int fds[2];
pid_t pid;

/* 創建管道。標識管道兩端的文件描述符會被放置在 fds 中。*/
pipe (fds);
/* 產生子進程。*/

pid = fork ();
if (pid == (pid_t) 0) {
/* 這里是子進程。關閉我們的寫入端描述符。*/
close (fds[1]);
/* 將讀取端連接到標准輸入*/
dup2 (fds[0], STDIN_FILENO);
/* 用 sort 替換子進程。*/
execlp ("sort", "sort", 0); ---????
}
else {
/* 這是父進程。*/
FILE* stream;
/* 關閉我們的讀取端描述符。*/
close (fds[0]);
/* 將寫入端描述符轉換為一個 FILE 對象,然后將信息寫入。*/
stream = fdopen (fds[1], "w");
fprintf (stream, "This is a test.\n");
fprintf (stream, "Hello, world.\n");
fprintf (stream, "My dog has fleas.\n");
fprintf (stream, "This program is great.\n");
fprintf (stream, "One fish, two fish.\n");
fflush (stream);
close (fds[1]);
/* 等待子進程結束。*/
waitpid (pid, NULL, 0);
}

return 0;
}

5.4.4 popen 和 pclose
管道的一個常見用途是與一個在子進程中運行的程序發送和接受數據。而 popen 和
pclose 函數簡化了這個過程。它取代了對pipe、fork、dup2、exec 和fdopen 的一系
列調用。
下面將使用了popen 和pclose 的列表 5.9 與之前一個例子(列表 5.8 )進行比較。

代碼列表 5.9 (popen.c)使用 popen 的示例

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

int main ()
{

FILE* stream = popen ("sort", "w");
fprintf (stream, "This is a test.\n");
fprintf (stream, "Hello, world.\n");
fprintf (stream, "My dog has fleas.\n");
fprintf (stream, "This program is great.\n");
fprintf (stream, "One fish, two fish.\n");
return pclose (stream);
}

通過調用popen 取代 pipe、fork、dup2 和execlp 等,一個子進程被創建以執行了
sort 命令,。第二個參數,” w”,指示出這個進程希望對子進程輸出信息。Popen 的返回值
是管道的一端;另外一端連接到了子進程的標准輸入。在數據輸出結束后,pclose 關閉了
子進程的流,等待子進程結束,然后將子進程的返回值作為函數的返回值返回給調用進程。
傳遞給popen 的第一個參數會作為一條shell 命令在一個運行/bin/sh的子進程中執
行。Shell會照常搜索 PAT H 環境變量以尋找應執行的程序。如果第二個參數是"r",函數會
返回子進程的標准輸出流以便父進程讀取子進程的輸出。如果第二個參數是"w" ,函數返回
子進程的標准輸入流一邊父進程發送數據。如果出現錯誤,popen 返回空指針。
調用pclose 會關閉一個由popen 返回的流。在關閉指定的流之后,pclose 將等待子
進程退出。
5.4.5 FIFO
先入先出(first-in, first-out, FIFO)文件是一個在文件系統中有一個名字的管道。任何
進程均可以打開或關閉FIFO;通過 FIFO 連接的進程不需要是彼此關聯的。FIFO 也被稱為
命名管道。
可以用mkfifo 命令創建FIFO;通過命令行參數指定FIFO 的路徑。例如,運行這個
命令將在/tmp/fifo 創建一個FIFO:
% mkfifo /tmp/fifo
% ls -l /tmp/fifo
prw-rw-rw- 1 samuel users 0 Jan 16 14:04 /tmp/fifo
ls輸出的第一個字符是p,表示這個文件實際是一個FIFO(命名管道)。在一個窗口
中這樣從FIFO 中讀取內容:
% cat < /tmp/fifo
在第二個窗口中這樣向 FIFO 中寫入內容:
% cat > /tmp/fifo
然后輸入幾行文字。每次你按下回車后,當前一行文字都會經由FIFO 發送到第一個窗
口。通過在第二個窗口中按Ctrl+D關閉這個FIFO。運行下面的命令刪除這個 FIFO:
% rm /tmp/fifo

創建 FIFO
通過編程方法創建一個FIFO 需要調用mkfifo 函數。第一個參數是要創建FIFO 的路
徑,第二個參數是被創建的 FIFO 的屬主、屬組和其它用戶權限。關於權限,第十章“安全”
的10.3 節“文件系統權限”中進行了介紹。因為管道必然有程序讀取信息、有程序寫入信
息,因此權限中必須包含讀寫兩種權限。如果無法成功創建管道(如當同名文件已經存在的
時候),mkfifo 返回-1 。當你調用 mkfifo 的時候需要包含<sys/types.h> 和

<sys/stat.h>。

訪問 FIFO
訪問FIFO與訪問普通文件完全相同。要通過FIFO通信,必須有一個程序打開這個FIFO
寫入信息,而另一個程序打開這個FIFO 讀取信息。底層I/O 函數(open、write 、read、
close 等,列舉在附錄B “底層 I/O ”中)或C 庫I/O 函數(fopen 、fprintf、fscanf、
fclose 等)均適用於訪問FIFO。
例如,要利用底層I/O 將一塊緩存區的數據寫入FIFO 可以這樣完成:
int fd = open (fifo_path, O_WRONLY);
write (fd, data, data_length);
close (fd);

利用C 庫I/O 從FIFO 讀取一個字符串可以這樣做:
FILE* fifo = fopen (fifo_path, "r");
fscanf (fifo, "%s", buffer");
fclose (fifo);

FIFO 可以有多個讀取進程和多個寫入進程。來自每個寫入進程的數據當到達
PIPE_BUF (Linux 系統中為 4KB )的時候會自動寫入 FIFO。並發寫入可能導致數據塊的互
相穿插。同步讀取也會出現相似的問題。

與Windows 命名管道的區別
Win32 操作系統的管道與Linux系統中提供的相當類似。(相關技術細節可以從Win32 庫
文檔中獲得印證。)主要的區別在於,Win32 系統上的命名管道的功能更接近套接字。Win32
命名管道可以用於連接處於同一個網絡中不同主機上的不同進程之間相互通信。Linux系統
中,套接字被用於這種情況。同時,Win32 保證同一個命名管道上的多個讀——寫連接不出
現數據交叉情況,而且管道可以用於雙向交流。


 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM