進程間通信:管道(1)
一、管道
當從一個進程連接數據流到另一個進程時,我們使用術語管道(pipe)。我們通常是把一個進程的輸出通過管道連接到另一個進程的輸入。對於shell命令來說,命令的連接是通過管道字符來完成的,如:cmd1 | cmd2
1、進程管道
可能最簡單的在兩個程序之間傳遞數據的方法就是使用popen和pclose函數了,它們的原型如下:
#include<stdio.h> FILE *popen(const char *command,const char *open_mode); int pclose(FILE *stream_to_close);
popen函數允許一個程序將另一個程序作為新進程來啟動,並可以傳遞數據給它或通過它接收數據。command字符串是要運行的程序名和相應的參數。open_mode必須是“r"或者“w”。這意味著我們不能調用另一個程序並同時對它進行讀寫操作。
2、pipe調用
pipe函數的原型如下:
#include<unistd.h> int pipe(int file_descriptor[2]);
pipe函數的參數是一個由兩個整數類型的文件描述符組成的數組的指針。該函數在數組中填上兩個新的文件描述符後返回0,如果失敗則返回-1並設置errno來表明失敗的原因。兩個返回的文件描述符以一種特殊的方式連接起來。寫到file_descriptor[1]的所有數據都可以從file_descriptor[0]讀回來。數據基於先進先出的原則進行處理,這意味著如果把字節1,2,3寫到file_descriptor[1],從file_descriptor[0]讀到的數據也會是1,2,3。(這裏使用的是文件描述符而不是文件流,所以我們必須用底層的read和write調用來訪問數據,而不是用文件流庫函數fread和fwrite。
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { int data_processed; int file_pipes[2]; const char some_data[] = "123"; char* data; pid_t fork_result; if (pipe(file_pipes) == 0) { fork_result = fork();if (fork_result == (pid_t)-1) { fprintf(stderr, "Fork failure"); exit(EXIT_FAILURE); } if (fork_result == (pid_t)0) { close(0); dup(file_pipes[0]); //close(file_pipes[0]); close(file_pipes[1]); read(file_pipes[0],data,8); printf("%s\n",data); //execlp("od", "od", "-c", (char *)0); exit(EXIT_FAILURE); } else { close(file_pipes[0]); data_processed = write(file_pipes[1], some_data,strlen(some_data)); close(file_pipes[1]); printf("%d - wrote %d bytes\n", (int)getpid(), data_processed); } } exit(EXIT_SUCCESS); }
運行後得到如下結果:
Wrote 3 bytes Read 3 bytes: 123
接下來,我們將學習如何在子進程中運行一個與其父進程完全不同的另外一個程序,而不是僅僅運行一個相同程序。我們用exec調用來完成這一工作。這裏的一個難點是,通過exec調用的進程需要知道應該訪問哪個文件描述符。在前面的例子中,因為子進程本身有file_pipes數據的一份副本,所以這並不成為問題。但經過exec調用後,情況就不一樣了,因為原先的進程已經被新的子進程替換了。為了解決這個問題,我們可以將文件描述符(它實際上只是一個數字)作為一個參數傳遞給用exec啟動的程序。
為了演示它是如何工作的,我們需要使用兩個程序,第一個程序是數據生產者,它負責創建管道和啟動子進程,而後者是數據消費者。
如下pipe3.c:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { int data_processed; int file_pipes[2]; const char some_data[] = "123"; char buffer[BUFSIZ + 1]; pid_t fork_result; memset(buffer, ‘\0‘, sizeof(buffer)); if (pipe(file_pipes) == 0) { fork_result = fork(); if (fork_result == (pid_t)-1) { fprintf(stderr, "Fork failure"); exit(EXIT_FAILURE); } if (fork_result == 0) { sprintf(buffer, "%d", file_pipes[0]); (void)execl("pipe4", "pipe4", buffer, (char *)0); exit(EXIT_FAILURE); } else { data_processed = write(file_pipes[1], some_data, strlen(some_data)); printf("%d - wrote %d bytes\n", getpid(), data_processed); } } exit(EXIT_SUCCESS); }
數據消費者程序pipe4.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); }
pipe3在程序中調用pipe4,運行pipe3時,我們得到如下結果:
19174 - wrote 3 bytes 19175 - read 3 bytes: 123
pipe3程序的開始部分和前面例子一樣,用pipe調用創建一個管道,然後用fork調用創建一個新進程。接下來,它用sprintf把讀取管道數據的文件描述符保存到一個緩沖區中,該緩沖區中的內容將構成pipe4程序的一個參數。我們通過execl調用來啟動pipe4程序。
3、把管道用作標準輸入和標準輸出
主要用到以下兩個函數:
#include<unistd.h> int dup(int file_descriptor); int dup2(int file_descriptor_one,int file_descriptor_two);
dup調用的目的是打開一個新的文件描述符,這與open調用有點類似。不同之處是,dup調用創建的新文件描述符與作為它的參數的那個已有文件描述符指向同一個文件(或管道)。對於dup函數來說,新的文件描述符總是取最小的可用值。而對於dup2函數來說,它所創建的新文件描述符或者與參數file_descriptor_two相同,或者是第一個大於該參數的可用值。
用close和dup函數對文件描述符進行處理,當我們關閉文件描述符0,然後調用dup函數,可得如下結果:
文件描述符 | 初始值 | 關閉文件描述符0後 | dup調用後 |
0 | 標準輸入 | {已關閉} | 管道文件描述符 |
1 | 標準輸出 | 標準輸出 | 標準輸出 |
2 | 標準錯誤輸出 | 標準錯誤輸出 | 標準錯誤輸出 |
3 | 管道文件描述符 | 管道文件描述符 | 管道文件描述符 |
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { int data_processed; int file_pipes[2]; const char some_data[] = "123"; pid_t fork_result; if (pipe(file_pipes) == 0) { fork_result = fork(); if (fork_result == (pid_t)-1) { fprintf(stderr, "Fork failure"); exit(EXIT_FAILURE); } if (fork_result == (pid_t)0) { close(0); dup(file_pipes[0]); close(file_pipes[0]); close(file_pipes[1]); execlp("od", "od", "-c", (char *)0); exit(EXIT_FAILURE); } else { close(file_pipes[0]); data_processed = write(file_pipes[1], some_data, strlen(some_data)); close(file_pipes[1]); printf("%d - wrote %d bytes\n", (int)getpid(), data_processed); } } exit(EXIT_SUCCESS); }
運行後得到如下結果:
19316 - wrote 3 bytes 0000000 1 2 3
0000003
進程間通信:管道(1)