Linux 上實現雙向程序間通訊管道

2021-03-31 08:56:32 字數 4141 閱讀 4254

級別: 中級

吳詠煒 (

adah@sh163.***)

本文闡述了乙個使用 socketpair 系統呼叫在 linux 上實現雙向程序通訊管道的方法,並提供了乙個實現。

問題和常見方法

linux 提供了 popen 和 pclose 函式(1) ,用於建立和關閉管道與另外乙個程序進行通訊。其介面如下:

file *popen(const char ****mand, const char *mode);

int pclose(file *stream);

遺憾的是,popen 建立的管道只能是單向的 -- mode 只能是 "r" 或 "w" 而不能是某種組合--使用者只能選擇要麼往裡寫,要麼從中讀,而不能同時在乙個管道中進行讀寫。實際應用中,經常會有同時進行讀寫的要求,比如,我們可能希望把文字資料送往 sort 工具排序後再取回結果。此時 popen 就無法用上了。我們需要尋找其它的解決方案。

有一種解決方案是使用 pipe 函式(2)建立兩個單向管道。沒有錯誤檢測的**示意如下:

int pipe_in[2], pipe_out[2];

pid_t pid;

pipe(&pipe_in); // 建立父程序中用於讀取資料的管道

pipe(&pipe_out); // 建立父程序中用於寫入資料的管道

if ( (pid = fork()) == 0) else

當然,這樣的**的可讀性(特別是加上錯誤處理**之後)比較差,也不容易封裝成類似於 popen/pclose 的函式,方便高層**使用。究其原因,是 pipe 函式返回的一對檔案描述符只能從第乙個中讀、第二個中寫(至少對於 linux 是如此)。為了同時讀寫,就只能採取這麼累贅的兩個 pipe 呼叫、兩個檔案描述符的形式了。

乙個更好的方案

使用pipe就只能如此了。不過,linux 實現了乙個源自 bsd 的 socketpair 呼叫 (3),可以實現上述在同乙個檔案描述符中進行讀寫的功能(該呼叫目前也是 posix 規範的一部分(4) )。該系統呼叫能建立一對已連線的(unix 族)無名 socket。在 linux 中,完全可以把這一對 socket 當成 pipe 返回的檔案描述符一樣使用,唯一的區別就是這一對檔案描述符中的任何乙個都可讀和可寫。

這似乎可以是乙個用來實現程序間通訊管道的好方法。不過,要注意的是,為了解決我前面的提出的使用 sort 的應用問題,我們需要關閉子程序的標準輸入通知子程序資料已經傳送完畢,而後從子程序的標準輸出中讀取資料直到遇到 eof。使用兩個單向管道的話每個管道可以單獨關閉,因而不存在任何問題;而在使用雙向管道時,如果不關閉管道就無法通知對端資料已經傳送完畢,但關閉了管道又無法從中讀取結果資料。——這一問題不解決的話,使用 socketpair 的設想就變得毫無意義。

令人高興的是,shutdown 呼叫 (5) 可解決此問題。畢竟 socketpair 產生的檔案描述符是一對 socket,socket 上的標準操作都可以使用,其中也包括 shutdown。——利用 shutdown,可以實現乙個半關閉操作,通知對端本程序不再傳送資料,同時仍可以利用該檔案描述符接收來自對端的資料。沒有錯誤檢測的**示意如下:

int fd[2];

pid_t pid;

socketpair(af_unix, socket_stream, 0, fd); // 建立管道

if ( (pid = fork()) == 0) else

很清楚,這比使用兩個單向管道的方案要簡潔不少。我將在此基礎上作進一步的封裝和改進。

封裝和實現

直接使用上面的方法,無論怎麼看,至少也是醜陋和不方便的。程式的維護者想看到的是程式的邏輯,而不是完成一件任務的各種各樣的繁瑣細節。我們需要乙個好的封裝。

封裝可以使用 c 或者 c++。此處,我按照 unix 的傳統,提供乙個類似於 posix 標準中 popen/pclose 函式呼叫的 c 封裝,以保證最大程度的可用性。介面如下:

file *dpopen(const char ****mand);

int dpclose(file *stream);

int dphalfclose(file *stream);

關於介面,以下幾點需要注意一下:

- 與 pipe 函式類似,dpopen 返回的是檔案結構的指標,而不是檔案描述符。這意味著,我們可以直接使用 fprintf 之類的函式,檔案緩衝區會快取寫入管道的資料(除非使用 setbuf 函式關閉檔案緩衝區),要保證資料確實寫入到管道中需要使用 fflush 函式。

- 由於 dpopen 返回的是可讀寫的管道,所以 popen 的第二個表示讀/寫的引數不再需要。

- 在雙向管道中我們需要通知對端寫資料已經結束,此項操作由dphalfclose函式來完成。

具體的實現請直接檢視程式源**,其中有詳細的注釋和 doxygen 文件注釋(6) 。我只略作幾點說明:

- 本實現使用了乙個鍊錶來記錄所有 dpopen 開啟的檔案指標和子程序 id 的對應關係,因此,在同時用 dpopen 開啟的管道的多的時候,dpclose(需要搜尋鍊錶)的速度會稍慢一點。我認為在通常使用過程中這不會產生什麼問題。如果在某些特殊情況下這會是乙個問題的話,可考慮更改 dpopen 的返回值型別和 dpclose 的傳入引數型別(不太方便使用,但實現簡單),或者使用雜湊表/平衡樹來代替目前使用的鍊錶以加速查詢(介面不變,但實現較複雜)。

- 當編譯時在 gcc 中使用了 "-pthread" 命令列引數時,本實現會啟用 posix 執行緒支援,使用互斥量保護對鍊錶的訪問。因此本實現可以安全地用於 posix 多執行緒環境之中。

- 與 popen 類似(7) ,dpopen 會在 fork 產生的子程序中關閉以前用 dpopen 開啟的管道。

- 如果傳給 dpclose 的引數不是以前用 dpopen 返回的非 null 值,當前實現除返回 -1 表示錯誤外,還會把 errno 設為 ebadf。對於 pclose 而言,這種情況在 posix 規範中被視為不確定(unspecified)行為 (8)。

- 實現中沒有使用任何平台相關特性,以方便移植到其它 posix 平台上。

下面的**展示了乙個簡單例子,將多行文字送到 sort 中,然後取回結果、顯示出來:

輸出結果為:

總結

本文闡述了乙個使用 socketpair 系統呼叫在 linux 上實現雙向程序通訊管道的方法,並提供了乙個實現。該實現提供的介面與 posix 規範中的 popen/pclose 函式較為接近,因而非常易於使用。該實現沒有使用平台相關的特性,因而可以不加修改或只進行少量修改即可移植到支援 socketpair 呼叫的 posix 系統中去。

dpopen.zip

參考資料http://.die.***/doc/linux/man/man3/popen.3.html

4. posix規範:

6. doxygen主頁:

7. posix規範:

8. posix規範:

關於作者

吳詠煒,目前在 linux 上從事高效能入侵檢測系統的研發。對於開發跨平台、高效能、可重用的 c++ **有著濃厚的興趣。

adah@sh163.*** 可以跟他聯絡。

全文出自 :

ibm developerworks 中國

Linux 上實現雙向程序間通訊管道

簡介 本文闡述了乙個使用 socketpair 系統呼叫在 linux 上實現雙向程序通訊管道的方法,並提供了乙個實現。問題和常見方法 linux 提供了 popen 和 pclose 函式 1 用於建立和關閉管道與另外乙個程序進行通訊。其介面如下 file popen const char com...

程序間通訊 雙向管道實現

linux 提供了 popen 和 pclose 函式 1 用於建立和關閉管道與另外乙個程序進行通訊。其介面如下 file popen const char command,const char mode int pclose file stream 遺憾的是,popen 建立的管道只能是單向的 m...

Linux 管道 實現程序間通訊

管道是一種最基本的 ipc 機制,由 pipe 函式建立 include int pipe int filedes 2 呼叫 pipe 函式時在核心中開闢一塊緩衝區 稱為管道 用於通訊,它有乙個讀端乙個寫端,然後通過 filedes 引數傳出給使用者程式兩個檔案描述符,filedes 0 指向管道的...