程序間通訊方式總結——管道(一)
Linux/Unix系統IPC是各種程序間通訊方式的統稱,但是其中極少能在所有Linux/Unix系統實現中進行移植。隨著POSIX和Open Group(X/Open)標準化的推進呵護影響的擴大,情況雖已得到改善,但差別仍然存在。一般來說,Linux/Unix常見的程序間通訊方式有:管道、訊息佇列、訊號、訊號量、共享記憶體、套接字等。博主將在《程序間通訊方式總結》系列博文中和大家一起探討學習程序間通訊的方式,並對其進行總結,讓我們共同度過這段學習的美好時光。這裡我們就以其中一種方式管道展開探討,管道是Linux/Unix系統IPC的最古老形式,並且所有Linux/Unix系統都提供此種通訊機制。管道又分為無名管道和有名管道,無名管道只能用於有親緣關係的程序間通訊,有名管道則可以用於非親緣程序間的通訊。本篇就以管道中的無名管道(又稱匿名管道)為例,希望對你有所幫助。
popen
如果你使用過Linux的命令,那麼對於管道這個名詞你一定不會感覺到陌生,因為我們通常通過符號“|"來使用管道,但是管理的真正定義是什麼呢?管道是一個程序連線資料流到另一個程序的通道,它通常是用來把一個程序的輸出通過管道連線到另一個程序的輸入。
FILE* popen (const char *command, const char *open_mode);
int pclose(FILE *stream_to_close);
popen函式允許一個程式將另一個程式作為新程序來啟動,並可以傳遞資料給它或者通過它接收資料。command是要執行的程式名和相應的引數。
如果open_mode是"r",主呼叫程式就可以使用被呼叫程式的輸出,通過函式返回的FILE指標,就可以通過stdio函式(如fread)來讀取程式的輸出;如果open_mode是"w",主呼叫程式就可以向被呼叫程式傳送資料,即通過stdio函式(如fwrite)向被呼叫程式寫資料,而被呼叫程式就可以在自己的標準輸入中讀取這些資料。
pclose函式用於關閉由
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BUFSIZE 1024
//標準流管道
int main()
{
//定義檔案指標
FILE *fpr = NULL;
FILE *fpw = NULL;
//儲存要執行的命令串
const char *cmdr = "ps x";
const char *cmdw = "grep pts";
//定義快取區並初始化
char buf[BUFSIZE] = {0};
memset(buf, 0x00, BUFSIZE);
//建立標準流管道,將執行命令的結果裝入管道
//引數1:執行的命令字串
//引數2:開啟的形式(只識別第一個字元)
//返回檔案指標
if (NULL == (fpr = popen(cmdr, "r")))perror("popen"), exit(EXIT_FAILURE);
if (NULL == (fpw = popen(cmdw, "w")))perror("popen"), exit(EXIT_FAILURE);
int readlen = 0;
//從管道中讀取資料存入buf中
while ((readlen = fread(buf, sizeof(char), BUFSIZE, fpr)) > 0)
{
buf[readlen] = '\0';
//將buf中的資料寫入fpw對應的管道中,作為cmdw命令的輸入資料
fwrite(buf, sizeof(char), readlen, fpw);
}
//關閉檔案指標
pclose(fpr);
pclose(fpw);
return 0;
}
程式執行結果:
在程式中,popen(cmdr,"r")將cmdr命令執行的結果寫入匿名管道,後面通過fpr從該匿名管道中讀取資料。popen(cmdw, "w"),fpw將資料寫入管道,作為cmdw命令執行的資料輸入。從程式執行和“ps x | grep pts”執行結果的對比,我們發現他們的執行結果完全一樣。相信你現在已經理解並掌握了popen的用法,下面我們來看看另一個無名管道的使用方式pipe。
pipe
int pipe(int fileds[2]);
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
intmain()
{
int fds[2] = {};
char buf[256] = "";
//建立匿名管道(只能在父子程序或親緣程序之間進行通訊)
if (-1 == pipe(fds)) strerror(errno),exit(1);
//建立程序
pid_t pid = fork();
if (-1 == pid) perror("fork:"),exit(1);
//子程序
if (0 == pid)
{
//關閉管道讀描述符
close(fds[0]);
fprintf(stdout, "--------子程序從終端讀取資料,並將讀取資料輸入管道中-------\n");
//從標準輸入讀取資料
fscanf(stdin, "%[^\n]", buf);
fscanf(stdin, "%*c");
//向管道中寫入資料
write(fds[1], buf, sizeof(buf));
//關閉管道的寫描述符
close(fds[1]);
}
else//父程序
{
//關閉管道的寫描述符
close(fds[1]);
char buf[256] = "";
//從管道中讀取資料
read(fds[0], buf, sizeof(buf));
fprintf(stdout, "--------父程序從管道中讀取資料,並輸出到終端--------\n");
fprintf(stdout, "%s\n", buf);
//關閉管道的讀描述符
close(fds[0]);
}
return 0;
}
程式執行結果:
下面來介紹一種用管道來連線兩個程序的更簡潔方法,我們可以把檔案描述符設定為一個已知值,一般是標準輸入0或標準輸出1。這樣做最大的好處是可以呼叫標準程式,即那些不需要以檔案描述符為引數的程式。
int dup(int file_descriptor);
int dup2(int file_descriptor_one, int file_descriptor_two);
dup呼叫建立一個新的檔案描述符與作為它的引數的那個已有檔案描述符指向同一個檔案或管道。對於dup函式而言,新的檔案描述總是取最小的可用值。而dup2所建立的新檔案描述符或者與int file_descriptor_two相同,或者是第一個大於該引數的可用值。也就是說dup是從0開始找第一個可用的檔案描述符,dup2則是從file_descriptor_two開始找第一個可用的檔案描述符,此外dup2會先關閉file_descriptor_two指定的檔案描述符。是不是感覺很暈哈,初次使用dup理解起來是要費點勁,讓我們看個栗子先。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
/*
*
*子程序將ls輸出到管道,父程序從管道中讀取資料並顯示到標準輸出端
*
*
*/
int main()
{
int fds[2] = {};
//建立匿名管道(只能在父子程序或親緣程序之間進行通訊)
if (-1 == pipe(fds)) fprintf(stdout, “pipe:%s”, strerror(errno)),exit(1);
//建立程序
pid_t pid = vfork();
if (-1 == pid) perror("fork:"), exit(1);
//子程序
//子程序會複製父程序已開啟的檔案描述符
if (0 == pid)
{
//關閉管道讀描述符
close(fds[0]);
//將標準輸出與管道寫入關聯
//close(1)
//dup(fds[1]);
//此語句與上面兩句等價
dup2(fds[1], 1);
//ls輸出到標準輸出端的資料實際輸出到管道中
execlp("ls", "ls", "-l", NULL);
//關閉管道寫描述符
close(fds[1]);
}
else//父程序
{
//sleep(5);
//父程序
//關閉管道寫描述符
close(fds[1]);
//將標準輸入與管道讀入關聯
dup2(fds[0], 0);
//wc從標準輸入端讀取資料,也就是從管道中讀取資料
execlp("wc", "wc", "-l", NULL);
//關閉管道讀描述符
close(fds[0]);
}
return 0;
}
程式執行結果:
程式的執行結果和“ls –l| wc –l”執行結果完全一致,從上面的程式中我們知道,dup2和dup的使用。在程式中,子程序將“ls -l”的執行資料輸出,原本應該輸出到標準輸出,但由於dup2將標準輸出和fds[1]關聯,也就是將輸出到標準輸出的資料實際上都輸入到管道中;符程序則將fds[0]和標準輸入相關聯,fds[0]從管道中讀取fds[1]寫入的資料,實際上就是標準輸入讀取fds[1]寫入管道中的資料。在父程序中呼叫execlp函式替換當前程序,執行”wc -l”命令,而wc需要資料輸入,這裡剛好就是fds[1]寫入管道中的資料,從fds[0]讀取管道資料作為”wc -l”的輸入。哈哈,是不是很easy啊,很開心吧,你又學會了一項新技能哦,離大牛更近了一步咯。
關於管道中的無名管道的學習我們就到此結束了,相信大家都有所收穫,希望小夥伴們都已經理解並掌握了無名管道的常用方法。下一篇我們將繼續總結管道,在那裡我們將一起學習有名管道的常用方式。當然,如果你覺得對程序間通訊的方式不勝瞭解,還有些許疑惑,請關注博主《程序間通訊方式總結》系列博文,相信你在那裡能找到答案。