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(只讀)"和"w(只寫)"其中之一。注意,popen函數的返回值是一個FILE類型的指針,而Linux把一切都視爲文件,也就是說我們可以使用stdio I/O庫中的文件處理函數來對其進行操作。
如果open_mode是"r",主調用程序就可以使用被調用程序的輸出,通過函數返回的FILE指針,就可以通過stdio函數(如fread)來讀取程序的輸出;如果open_mode是"w",主調用程序就可以向被調用程序發送數據,即通過stdio函數(如fwrite)向被調用程序寫數據,而被調用程序就可以在自己的標準輸入中讀取這些數據。
pclose函數用於關閉由popen創建出的關聯文件流。pclose只在popen啓動的進程結束後才返回,如果調用pclose時被調用進程仍在運行,pclose調用將等待該進程結束。它返回關閉的文件流所在進程的退出碼。說了這麼多,沒有使用過popen的小夥伴可能已經暈了,下面我們就來舉一個簡單的栗子吧。
#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啊,很開心吧,你又學會了一項新技能哦,離大牛更近了一步咯。
關於管道中的無名管道的學習我們就到此結束了,相信大家都有所收穫,希望小夥伴們都已經理解並掌握了無名管道的常用方法。下一篇我們將繼續總結管道,在那裏我們將一起學習有名管道的常用方式。當然,如果你覺得對進程間通信的方式不勝瞭解,還有些許疑惑,請關注博主《進程間通信方式總結》系列博文,相信你在那裏能找到答案。