程序間的通訊:管道
我們知道在兩個程序間傳送訊息的非常簡單的方法:使用訊號,我們建立通知事件,通過它引起響應,但傳送的資訊只限於一個訊號值,所以在此我們將介紹管道,通過它程序之間可以交換更有用的資料。
一、什麼是管道:通常是把一個程序得輸出通過管道連線到另一個程序得輸入。對於shell命令來說,命令的連線是通過管道字元來完成的,如下:
cmd1 | cmd2 shell負責安排兩個命令的標準輸入和標準輸出
- cmd1的標準輸入來自終端鍵盤
- cmd1的標準輸出傳遞給cmd2,作為它的標準輸入
- cmd2的標準輸出連線到終端螢幕上
二、程序管道
最簡單的在兩個程式之間傳遞資料的方法是使用popen和pclose函式
標頭檔案 #include<stdio.h>
FILE *popen(const char *command, const char *open_mode);
int pclose(FILE *stream_to_close);
1、popen函式:
(1)允許一個程式將另一個程式作為新程序來啟動,並可以傳遞資料給它或者通過它接收資料。
(2)command字串是要執行的程式名和相應的引數
(3)open_mode必須是“r” 或者“w”,這意味著我們不能呼叫另一個程式並同時對它進行讀寫操作。popen函式在失敗是返回一個空指標,如果想通過管道實現雙向通訊,最普通的結局方法就是使用兩個管道,每個管道負責一個方向的資料流
- 如果是“r”,被呼叫程式的輸出就可以被呼叫者程式使用,呼叫程式利用popen函式返回FILE *檔案流指標,就可以通過常用的stdio庫函式(如fread)來讀取呼叫程式的輸出。
- 如果是“w” 呼叫程式就可以用fwrite呼叫向被呼叫程式傳送資料,而被呼叫程式可以在自己的標準輸入上讀取這些資料。被呼叫的程式通常不會意識到自己正在從另一個程序讀取資料,它只是在標準輸入流上讀取資料,然後做出相應的操作
2、pclose函式:
(1)用popen啟動的程序結束時,我們可以用pclose函式關閉與之關聯的檔案流。pclose呼叫只在popen啟動的程序結束後才返回,如果呼叫pclose時它仍在執行,pclose呼叫將等待該程序的結束
(2)pclose呼叫的返回值通常時它所關閉的檔案流所在程序的退出碼。如果呼叫程序在呼叫pclose之前執行一個wait語句,被呼叫程序的退出狀態就會丟失,因為被呼叫程序已結束。此時,pclose將返回-1,並設定errno為ECHILD。
- 現在我們寫一個簡單的讀取外部程式的輸出的程式碼:
//捕獲uname命令的輸出
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#define BUFSIZE 128
int main()
{
FILE *read_fp; //設定檔案流指標
char buffer[BUFSIZE];
int chars_read;
memset(buffer,'\0',sizeof(buffer)); //全部清零
read_fp = popen("uname -a","r");
if(read_fp != NULL)
{
chars_read = fread(buffer,sizeof(char),BUFSIZE,read_fp);
if(chars_read > 0)
{
printf("Outpup was:-\n%s",buffer);
}
pclose(read_fp);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
結果顯示:
uname -a 的作用是列印系統資訊,包括計算機型號、作業系統名稱、版本和發行號,以及計算機的網路名。
- 將輸出送往popen
程式碼實現:
//將輸出送往外部程式
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#define BUFSIZE 128
int main()
{
FILE *write_fp;
char buffer[BUFSIZE];
sprintf(buffer, "Once upon a time, there was ...\n");
write_fp = popen("od -c","w");
if(write_fp != NULL)
{
fwrite(buffer,sizeof(char),strlen(buffer),write_fp);
pclose(write_fp);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
結果展示:
程式使用帶引數“w”的popen啟動od -c命令,這樣就可以向該命令傳送資料了,然後它給od -c命令傳送一個字串,該命令接收並處理它,最後把處理結果列印到自己的標準輸出上。
3、pipe呼叫
前面所講的popen函式屬於高階的函式,現在講述底層的pipe函式,通過pipe函式在兩個程式之間傳遞資料不需要啟動一個shell來解釋請求的命令,它同時還提供了對讀寫資料的更多控制。
- 標頭檔案 <unistd.h>
- int pipe(int file_descriptor[2]); //引數是一個由兩個整數型別的檔案描述符組成的陣列的指標。該函式在陣列中填上兩個新的檔案描述符後返回0,如果失敗則返回-1並設定errno來表明失敗的原因。
- EMFILE:程序使用的檔案描述符過多
- ENFILE:系統的檔案表已滿
- EFAULT:檔案描述符無效
兩個返回的檔案描述符以一種特殊的方式連線起來,寫到file_descriptor[1]的所有資料都可以從file_descriptor[0]讀回來,資料基於先進先出的原則進行處理,這意味著如果你把位元組1,2,3寫到file_descriptor[1],從file_descriptor[0]讀取到的資料也會是1,2,3。
特別注意的是:這裡使用的是檔案描述符而不是檔案流,所以我們必須用底層的read和write呼叫來訪問資料,而不是檔案流庫函式fread和fwrite。
注:file_pipe陣列的用法,它的地址被當作引數傳遞給pipe函式
程式碼實現:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#define BUFSIZE 128
int main()
{
int data_processed;
int file_pipes[2];
const char some_data[] = "123";
char buffer[BUFSIZE];
memset(buffer,'\0',sizeof(buffer));
if(pipe(file_pipes) == 0)
{
data_processed = write(file_pipes[1],some_data,strlen(some_data));
printf("Wrote %d bytes\n",data_processed);
data_processed = read(file_pipes[0], buffer,BUFSIZE);
printf("Read %d bytes :%s\n",data_processed,buffer);
exit(EXIT_SUCCESS);
}
exit(EXIT_SUCCESS);
}
結果展示:
這個程式用陣列file_pipes[]中的兩個檔案描述符建立一個管道,然後它用檔案描述符file_pipes[1]向管道中寫資料,再從file_pipes[0]讀回資料,注:管道有一些內建的快取區,它在write和read呼叫之間儲存資料。