1. 程式人生 > >程序間的通訊:管道

程序間的通訊:管道

我們知道在兩個程序間傳送訊息的非常簡單的方法:使用訊號,我們建立通知事件,通過它引起響應,但傳送的資訊只限於一個訊號值,所以在此我們將介紹管道,通過它程序之間可以交換更有用的資料。

一、什麼是管道:通常是把一個程序得輸出通過管道連線到另一個程序得輸入。對於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呼叫之間儲存資料。