
程序間通訊 - 管道
管道是兩個或多個相關或相互關聯的程序之間的通訊媒介。它可以存在於一個程序內部,也可以是子程序和父程序之間的通訊。通訊也可以是多層次的,例如父程序、子程序和孫程序之間的通訊等。通訊是透過一個程序寫入管道,另一個程序從管道讀取來實現的。要實現管道系統呼叫,需要建立兩個檔案,一個用於寫入檔案,另一個用於讀取檔案。
管道機制可以用一個即時場景來理解,例如用管道往某個容器(比如水桶)裡灌水,有人用杯子取水。灌水的過程就是寫入管道,取水的過程就是從管道讀取。這意味著一個輸出(水)是另一個的輸入(水桶)。

#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 - 根據需要執行通訊。

示例程式
示例程式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