程序間通訊 - 管道



管道是兩個或多個相關或相互關聯的程序之間的通訊媒介。它可以存在於一個程序內部,也可以是子程序和父程序之間的通訊。通訊也可以是多層次的,例如父程序、子程序和孫程序之間的通訊等。通訊是透過一個程序寫入管道,另一個程序從管道讀取來實現的。要實現管道系統呼叫,需要建立兩個檔案,一個用於寫入檔案,另一個用於讀取檔案。

管道機制可以用一個即時場景來理解,例如用管道往某個容器(比如水桶)裡灌水,有人用杯子取水。灌水的過程就是寫入管道,取水的過程就是從管道讀取。這意味著一個輸出(水)是另一個的輸入(水桶)。

Pipe with one
#include<unistd.h>

int pipe(int pipedes[2]);

此係統呼叫將建立一個單向通訊管道,即它建立兩個描述符,第一個連線到從管道讀取,另一個連線到寫入管道。

描述符pipedes[0]用於讀取,pipedes[1]用於寫入。寫入pipedes[1]的內容可以從pipedes[0]讀取。

此呼叫成功時返回零,失敗時返回-1。要了解失敗原因,請檢查errno變數或perror()函式。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

即使檔案的基本操作是讀取和寫入,在執行操作之前開啟檔案並在完成所需操作後關閉檔案也是必要的。通常,每個程序預設開啟3個描述符,用於輸入(標準輸入 – stdin)、輸出(標準輸出 – stdout)和錯誤(標準錯誤 – stderr),檔案描述符分別為0、1和2。

此係統呼叫將返回一個用於進一步檔案讀取/寫入/查詢(lseek)操作的檔案描述符。通常,檔案描述符從3開始,並隨著開啟的檔案數量增加而遞增。

傳遞給open系統呼叫的引數是路徑名(相對路徑或絕對路徑)、標誌(說明開啟檔案的目的,例如,以讀取方式開啟,O_RDONLY,以寫入方式開啟,O_WRONLY,以讀取和寫入方式開啟,O_RDWR,追加到現有檔案O_APPEND,如果不存在則建立檔案O_CREAT等等)和所需的模式,提供使用者或所有者/組/其他使用者的讀/寫/執行許可權。模式可以用符號表示。

讀取 – 4,寫入 – 2,執行 – 1。

例如:八進位制值(以0開頭),0764表示所有者具有讀、寫和執行許可權,組具有讀和寫許可權,其他使用者具有讀許可權。這也可以表示為S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH,這意味著0700|0040|0020|0004 → 0764的或運算。

此係統呼叫成功時返回新的檔案描述符ID,出錯時返回-1。錯誤原因可以使用errno變數或perror()函式確定。

#include<unistd.h>

int close(int fd)

上述系統呼叫用於關閉已開啟的檔案描述符。這意味著該檔案不再使用,並且與其關聯的資源可以被任何其他程序重用。此係統呼叫成功時返回零,出錯時返回-1。錯誤原因可以使用errno變數或perror()函式確定。

#include<unistd.h>

ssize_t read(int fd, void *buf, size_t count)

上述系統呼叫是從指定檔案讀取資料,引數為檔案描述符fd、已分配記憶體的緩衝區(靜態或動態)和緩衝區大小。

檔案描述符ID用於標識相應的檔案,該ID在呼叫open()或pipe()系統呼叫後返回。在從檔案讀取之前,需要開啟該檔案。在呼叫pipe()系統呼叫時,它會自動開啟。

此呼叫成功時返回讀取的位元組數(如果遇到檔案結尾則返回零),失敗時返回-1。返回的位元組數可能小於請求的位元組數,如果資料不可用或檔案已關閉。失敗時會設定正確的錯誤編號。

要了解失敗原因,請檢查errno變數或perror()函式。

#include<unistd.h>

ssize_t write(int fd, void *buf, size_t count)

上述系統呼叫是向指定檔案寫入資料,引數為檔案描述符fd、已分配記憶體的緩衝區(靜態或動態)和緩衝區大小。

檔案描述符ID用於標識相應的檔案,該ID在呼叫open()或pipe()系統呼叫後返回。

在向檔案寫入之前,需要開啟該檔案。在呼叫pipe()系統呼叫時,它會自動開啟。

此呼叫成功時返回寫入的位元組數(如果未寫入任何內容則返回零),失敗時返回-1。失敗時會設定正確的錯誤編號。

要了解失敗原因,請檢查errno變數或perror()函式。

示例程式

以下是一些示例程式。

示例程式1 - 使用管道寫入和讀取兩條訊息的程式。

演算法

步驟1 - 建立管道。

步驟2 - 向管道傳送訊息。

步驟3 - 從管道檢索訊息並將其寫入標準輸出。

步驟4 - 向管道傳送另一條訊息。

步驟5 - 從管道檢索訊息並將其寫入標準輸出。

注意 - 也可以在傳送所有訊息後檢索訊息。

原始碼:simplepipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   
   printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 1 is %s\n", readmessage);
   printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 2 is %s\n", readmessage);
   return 0;
}

注意 - 理想情況下,需要檢查每個系統呼叫的返回狀態。為了簡化過程,並非所有呼叫的檢查都已完成。

執行步驟

編譯

gcc -o simplepipe simplepipe.c

執行/輸出

Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell

示例程式2 - 使用父程序和子程序透過管道寫入和讀取兩條訊息的程式。

演算法

步驟1 - 建立管道。

步驟2 - 建立子程序。

步驟3 - 父程序寫入管道。

步驟4 - 子程序從管道檢索訊息並將其寫入標準輸出。

步驟5 - 再次重複步驟3和步驟4。

原始碼:pipewithprocesses.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   int pid;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   pid = fork();
   
   // Child process
   if (pid == 0) {
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
   } else { //Parent process
      printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
      write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
      printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
      write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
   }
   return 0;
}

執行步驟

編譯

gcc pipewithprocesses.c –o pipewithprocesses

執行

Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello

使用管道進行雙向通訊

管道通訊被視為單向通訊,即父程序寫入而子程序讀取,反之亦然,但不能同時進行。但是,如果父程序和子程序都需要同時寫入和讀取管道,則解決方案是使用管道進行雙向通訊。需要兩個管道才能建立雙向通訊。

實現雙向通訊的步驟如下:

步驟1 - 建立兩個管道。第一個管道用於父程序寫入,子程序讀取,例如pipe1。第二個管道用於子程序寫入,父程序讀取,例如pipe2。

步驟2 - 建立子程序。

步驟3 - 關閉不需要的端點,因為每個通訊只需要一個端點。

步驟4 - 在父程序中關閉不需要的端點,即pipe1的讀取端和pipe2的寫入端。

步驟5 - 在子程序中關閉不需要的端點,即pipe1的寫入端和pipe2的讀取端。

步驟6 - 根據需要執行通訊。

Pipe with two

示例程式

示例程式1 - 使用管道實現雙向通訊。

演算法

步驟1 - 建立pipe1,用於父程序寫入,子程序讀取。

步驟2 - 建立pipe2,用於子程序寫入,父程序讀取。

步驟3 - 關閉父程序和子程序側管道不需要的端點。

步驟4 - 父程序寫入訊息,子程序讀取並在螢幕上顯示。

步驟5 - 子程序寫入訊息,父程序讀取並在螢幕上顯示。

原始碼:twowayspipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds1[2], pipefds2[2];
   int returnstatus1, returnstatus2;
   int pid;
   char pipe1writemessage[20] = "Hi";
   char pipe2writemessage[20] = "Hello";
   char readmessage[20];
   returnstatus1 = pipe(pipefds1);
   
   if (returnstatus1 == -1) {
      printf("Unable to create pipe 1 \n");
      return 1;
   }
   returnstatus2 = pipe(pipefds2);
   
   if (returnstatus2 == -1) {
      printf("Unable to create pipe 2 \n");
      return 1;
   }
   pid = fork();
   
   if (pid != 0) // Parent process {
      close(pipefds1[0]); // Close the unwanted pipe1 read side
      close(pipefds2[1]); // Close the unwanted pipe2 write side
      printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
      write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
      read(pipefds2[0], readmessage, sizeof(readmessage));
      printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
   } else { //child process
      close(pipefds1[1]); // Close the unwanted pipe1 write side
      close(pipefds2[0]); // Close the unwanted pipe2 read side
      read(pipefds1[0], readmessage, sizeof(readmessage));
      printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
      printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
      write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
   }
   return 0;
}

執行步驟

編譯

gcc twowayspipe.c –o twowayspipe

執行

In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello
廣告