基于STREAMS的管道(簡稱STREAMS管道,STREAMS pipe)是一個雙向(全雙工)管道。單個STREAMS管道就能向父、子進程提供雙向的數據流。
將http://www.CUOXin.com/nufangrensheng/p/3560130.html中圖15-1中的單向箭頭全部換成雙向箭頭,即為觀察STREAMS管道的兩種方式。
圖17-1 觀察STREAMS管道的兩種方式
如果從內部觀察STREAMS管道(圖17-2),可以看到它簡單得只包含兩個流首,每個流首的寫隊列(WQ)指向另一個流首的讀隊列(RQ),寫入管道一端的數據被放入另一端的讀隊列的消息中。
圖17-2 STREAMS管道的內部結構
因為STREAMS管道是一個流,所以可將STREAMS模塊壓入到該管道的任一端(圖17-3)。但是,如果我們在一端壓入了一個模塊,那么并不能在另一端彈出該模塊。如果想要刪除它,則必須從原壓入端刪除。
圖17-3 帶模塊的STREAMS管道內部結構
實例
下面用一個STREAMS管道再次實現程序清單15-9(http://www.CUOXin.com/nufangrensheng/p/3561379.html)中的協同進程實例。程序清單17-1是新的main函數。add2協同進程與程序清單15-8(http://www.CUOXin.com/nufangrensheng/p/3561379.html)中的相同。本程序調用了創建單個STREAMS管道的新函數s_pipe(見下個實例)。
程序清單17-1 用STREAMS管道驅動add2過濾進程的程序
#include "apue.h"static void sig_pipe(int); /* our signal handler */intmain(void){ int n; int fd[2]; pid_t pid; char line[MAXLINE]; if(signal(SIGPIPE, sig_pipe) == SIG_ERR) err_sys("signal error"); if(s_pipe(fd) < 0) /* need only a single stream pipe */ err_sys("pipe error"); if((pid = fork()) < 0) { err_sys("fork error"); } else if(pid > 0) { close(fd[1]); /* parent */ while(fgets(line, MAXLINE, stdin) != NULL) { n = strlen(line); if(write(fd[0], line, n) != n) err_sys("write error to pipe"); if((n = read(fd[0], line, MAXLINE)) < 0) err_sys("read error from pipe"); if(n == 0) { err_msg("child closed pipe"); break; } line[n] = 0; /* null terminate */ if(fputs(line, stdout) == EOF) err_sys("fputs error"); } if(ferror(stdin)) err_sys("fgets error on stdin"); exit(0); } else { close(fd[0]); if(fd[1] != STDIN_FILENO && dup2(fd[1], STDIN_FILENO) != STDIN_FILENO) err_sys("dup2 error to stdin"); if(fd[1] != STDOUT_FILENO && dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO) err_sys("dup2 error to stdout"); if(execl("./add2", "add2", (char *)0) < 0) err_sys("execl error"); } exit(0);}static voidsig_pipe(int signo){ PRintf("SIGPIPE caught/n"); exit(1);}
父進程只使用fd[0],子進程只是用fd[1]。因為STREAMS管道的每一端都是全雙工的,所以父進程讀、寫fd[0],而子進程將fd[1]復制到標準輸入和標準輸出。圖17-4顯示了由此構成的各描述符。
圖17-4 為協作進程所作的描述符安排
s_pipe函數定義為與標準pipe函數類似。它的調用參數與pipe相同,但是返回的描述符以讀-寫模式打開。
實例:基于STREASMS的s_pipe函數
程序17-2 基于STREAMS的s_pipe函數版本(它只是簡單地調用創建全雙工管道的標準pipe函數)
#include "apue.h"/** Return a STREAMS-based pipe, with the two file descirptors * returned in fd[0] and fd[1].*/ints_pipe(int fd[2]){ return(pipe(fd));}
注意,POSIX.1允許實現支持全雙工管道。對于這些實現,filedes[0]和filedes[1]以讀/寫方式打開(http://www.CUOXin.com/nufangrensheng/p/3560130.html)。但是并不是所有實現都支持pipe創建全雙工管道。對于不支持pipe創建全雙工管道的系統上面實例運行會出錯:“Bad file descriptor”。此時,我們把s_pipe函數中return語句 改為:return(socketpair(AF_UNIX, SOCK_STREAM, 0, fd));即可,這里使用了UNIX域套接字接口,詳情請參考高級進程間通信之UNIX域套接字。
1、命名的STREAMS管道
通常,管道僅在相關進程之間使用:子進程繼承父進程的管道。在http://www.CUOXin.com/nufangrensheng/p/3561632.html曾介紹,無關進程可以使用FIFO進行通信,但是這僅僅提供單向通信。STREAMS機制提供了一種途徑,使得進程可以給予管道一個文件系統中的名字。這就避免了單向FIFO的問題。
我們可以用fattach函數給STREAMS管道一個文件系統中的名字。
#include <stropts.h>int fattach(int filedes, const char *path);返回值:若成功則返回0,若出錯則返回-1
path參數必須引用一個現存的文件,調用進程應當或者擁有該文件并且對它具有寫權限,或者正在以超級用戶特權運行。
一旦STREAMS管道連接到文件系統名字空間,那么原來使用該名字的底層文件就不再是可訪問的。打開該名字的任一進程將能訪問相應管道,而不是訪問原先的文件。在調用fattach之前打開底層文件的任一進程可以繼續訪問該文件。確實,一般而言,這些進程并不知道該名字現在引用了另外一個文件。
圖17-5顯示了連接到路徑名/tmp/pipe的一條通道。只有管道的一端連接到文件系統中一個名字上。另一端用來與打開該連接文件名的進程通信。雖然fattach函數可將任何種類的STREAMS文件描述符與文件系統中的一個名字相連接,但它最主要用于將一個名字給予一個STREAMS管道。
圖17-5 一條管道安裝到文件系統的一個名字上
一個進程可以調用fdetach函數撤銷STREAMS管道文件與文件系統中名字的關聯關系。
#include <stropts.h>int fdetach(const char *path);返回值:若成功則返回0,若出錯則返回-1
在調用fdetach函數之后,先前依靠打開path而能訪問STREAMS管道的進程仍可繼續訪問該管道,但是在此之后打開path的進程將訪問駐留在文件系統中的底層文件。
2、唯一連接
雖然我們可以將STREAMS管道的一端連接到文件系統的名字空間,但是如果多個進程都想要用命名STREAMS管道與服務器進程通信,那么仍然存在問題。若幾個客戶進程同時將數據寫至一管道,那么這些數據就會混合交錯。即使我們保證客戶進程的字節數小于PIPE_BUF,使得寫操作是原子性的,但是仍無法保證服務器進程將數據送回所期望的某個客戶進程,也無法保證該客戶進程一定會讀此消息。當多個客戶進程同時讀一管道時,我們無法調度具體哪一個客戶進程去讀我們所發送的消息。
connld STREAMS模塊解決了這一問題。在將一個STREAMS管道連接到文件系統的一個名字之前,服務器進程可將connld模塊壓入要被連接管道的一端。其結果示于圖17-6。
在圖17-6中,服務器進程已將管道的一端連接至/tmp/pipe。我們用虛線指示客戶進程正在打開所連接的STREAMS管道。一旦打開操作完成,則服務器進程、客戶進程和STREAMS管道之間的關系示于圖17-7中。
圖17-7 用connld構造唯一連接
客戶進程絕不會接收到它所打開管道端的打開文件描述符。作為替代,操作系統創建了一個新管道,對客戶進程返回其一端,作為它打開/tmp/pipe的結果。系統將此新管道另一端的文件描述符經由已存在的連接管道發送給服務器進程,結果在客戶進程和服務器進程之間構成了唯一連接。
現在,我們將開發三個函數,使用這些函數可以創建在無關進程之間的唯一連接。這些函數模仿了在http://www.CUOXin.com/nufangrensheng/p/3565858.html中討論過的面向連接的套接字函數。在此處,我們使用STREAMS管道作為底層通信機制,在高級進程間通信之UNIX域套接字章節我們則將見到用UNIX域套接字實現的同樣這三個函數。
#include "apue.h"int serv_listen(const char *name);返回值:若成功則返回要偵聽的文件描述符,若出錯則返回負值int serv_accept(int listenfd, uid_t *uidptr);返回值:若成功則返回新文件描述符,若出錯則返回負值int cli_conn(const char *name);返回值:若成功則返回文件描述符,若出錯則返回負值
服務器進程調用serv_listen函數聲明它要在一個眾所周知的名字(文件系統中的某個路徑名)上偵聽客戶進程的連接請求。當客戶進程想要連接到服務器進程時,它們將使用該名字。serv_listen函數的返回值是STREAMS管道的服務器進程端。
程序清單17-3 使用STREAMS管道的serv_listen函數
#include "apue.h"#include <fcntl.h>#include <stropts.h>/* pipe permissions: user rw, group rw, others rw */#define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)/** Establish an endpoint to listen for connect requests.* Returns fd if all ok, <0 on error*/int serv_listen(const char *name){ int tempfd; int fd[2]; /* * Create a file: mount point for fattach(). */ unlink(name); if((tempfd = creat(name, FIFO_MODE)) < 0) return(-1); if(close(tempfd) < 0) return(-2); if(pipe(fd) < 0) return(-3); /* * Push connld & fattach() on fd[1]. */ if(ioctl(fd[1], I_PUSH, "connld") < 0) { close(fd[0]); close(fd[1]); return(-4); } if(fattach(fd[1], name) < 0) { close(fd[0]); close(fd[1]); return(-5); } close(fd[1]); /* fattach holds this end open */ return(fd[0]); /* fd[0] is where client connections arrive */ }
服務器進程使用serv_accept函數等待客戶進程連接請求的到達。當一個請求到達時,系統自動創建一個新的STREAMS管道,serv_accept函數向服務器進程返回該STREAMS管道的另一端。另外,客戶進程的有效用戶ID存放在uidptr指向的存儲區中。
程序清單17-4 使用STREAMS管道的serv_accept函數
#include "apue.h"#include <stropts.h>/** Wait for a client connection to arrive, and accept it.* We also obtain the client's user ID.* Return new fd if all ok, <0 on error.*/int serv_accept(int listenfd, uid_t *uidptr){ struct strrecvfd recvfd; if(ioctl(listenfd, I_RECVFD, &recvfd) < 0) return(-1); /* could be EINTR if signal caught */ if(uidptr != NULL) *uidptr = recvfd.uid; /* effective uid of caller */ return(recvfd.fd); /* return the new descriptor */}
客戶進程調用cli_conn函數連接至服務器進程??蛻暨M程指定的參數name必須與服務器進程調用serv_listen函數時所用的相同。函數返回時,客戶進程得到連接至服務器進程的文件描述符。
程序清單17-5 用STREAMS管道的cli_conn函數
#include "apue.h"#include <fcntl.h>#include <stropts.h>/** Create a client endpoint and connect to a server.* Return fd if all ok, <0 on error.*/intcli_conn(const char *name){ int fd; /* open the mounted stream */ if((fd = open(name, O_RDWR)) < 0) return(-1); if(isastream(fd) == 0) { close(fd); return(-2); } return(fd);}
我們對返回的描述符是否引用STREAMS設備進行了二次檢驗,以防止服務器進程沒被啟動而路徑名仍存在于文件系統中。
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www.apuebook.com/。
新聞熱點
疑難解答