(BSD不支持STREAMS,而Linux的LiS(Linux STREAMS)早已过期,取而代之的fast
STEAMS的最新版本也只是2008年,只支持Linux2.x版本,在我的Linux
3.0上无法运行。加上STREAMS的功能可以用套接字实现,所以我打算只概括地了解下它的概念,对于代码例子,我就直接拷贝过来,随便看看,不做试验
了。)
一个基于STREAMS的管道(简称为STREAMS管道)是一个双向的(全双工)管道。为了在父进程和子进程之间得到双向的数据流,只需要单个STRAMS管道。
回想15.1节STREAMS管道被Solaris支持,并在Linux上作为一个插件包。
有两种角度来观察STREAMS管道。一种是管道两端在同一个进程里首尾相连,数据双向流动。另一种是两端通过内核的流管道相连。它和15.2节里的描述唯一的区别是数据是双向流动的,因为STREAMS管道是全双工的。
如果我们看一下STREAMS管道的内部,那么我们看到它是简单的两个流头,每个的写队列(WQ)都指向对方的写队列(RQ)。写到管道一端的数据会被放置到另一个的读队列里。
因为一个STREAMS管道是一个流,我们可以把一个STREAMS模块推到管道的任一端来处理写到管道的数据(如下图所示)。但是如果我们把一个模块推到某端,则我们不能在另一端弹出它。如果我们想要删除它,我们需要在我们压入它的那端删除它。
假
定我们不做任何特别的事,比如推一个模块,那么一个STREAMS管道和非STREAMS管道的行为一样,除了它支持多数在streamio里描述时
STREAMS的ioctl命令。在17.2.2节,我们将看到一个向STREAMS管道推一个模块来提供唯一的连接,当我们在文件系统给管道一个名字
时。
让我们用单个STREAMS管道重新实现协进程的例子。下面的代码展示一个新的main函数。add2协进程和15.4节那个是相同
的。我们调用一个新的函数,s_pipe,来创建单个STREAMS管道。(我们马上会展示这个函数的STREAMS管道版本和UNIX域套接字版本。)
- #include "apue.h"
- static void sig_pipe(int); /* our signal handler */
- int
- main(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) { /* parent */
- close(fd[1]);
- 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 { /* child */
- 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 void
- sig_pipe(int signo)
- {
- printf("SIGPIPE caught\n");
- exit(1);
- }
父进程只使用了fd[0],而子进程只使用了fd[1]。因为STREAMS的各端都是全双工的,所以父进程读并写fd[0],而子进程把
fd[1]同时复制到标准输入和标准输出。注意这个例子也和不基于STREAMS的全双工管道一起工作,因为它不使用任何除了基于STREAMS的管道的
与生俱来的全双工本质之外的STREAMS特性。
回想15.1节FreeBSD支持全双工管道,但是这些管道不是基于STREAMS机制的。
我们定义和标准pipe函数相似的函数s_pipe。两个函数都接受相同的参数,但是s_pipe返回的描述符打开来可以同时读写。
下面的代码展示了s_pipe函数的基于STREAMS的版本。这个版本简单地调用创建一个全双工管道的标准pipe函数。
- #include "apue.h"
- /*
- * Returns a STREAMS-based pipe, with the two file descriptors
- * returned in fd[0] and fd[1].
- */
- int
- s_pipe(int fd[2])
- {
- return(pipe(fd));
- }
17.2.1 命名STREAMS管道
通常,管道可以只在相关进程之间使用:子进程从父进程继承管道。在15.5节,我们看到了无关进程可以使用FIFO来通信,但是它只提供了单向的通信路径。STREAMS路径为进程提供一种方法在文件系统里给一个管道一个名字。这绕过了处理单向FIFO的问题。
我们可以使用fattach函数来在文件系统给一个STREAMS管道一个名字。
- #include <stropts.h>
- int fattach(int filedes, const char *path);
- 成功返回0,错误返回-1。
path参数必须引用一个已有的文件,调用进程必须拥有这个文件,或有对它有写的权限,或使用超级用户特权运行。
一旦STREAMS管道被附加到文件系统命名空间上时,底下的文件是不可访问的。任何打开这个名字的进程将得到这个管道的访问,不是底下的文件。事实上,这些进程通常会不知道这个名字现在引用到一个不同的文件。
管道只有一端被附加到文件系统的一个名字。另一端用来和打开这个附加的文件名的进程通信。即使它可以附加任何类型的STREAMS文件到文件系统的一个名字,fattach函数普遍用来给一个STREAMS管道一个名字。
一个进程可以调用fdetach来撤消一个STREAMS文件和文件系统里的名字之间的关联。
- #include <stropts.h>
- int fdetach(const cha *path);
- 成功返回0,错误返回-1。
在fdetach被调用后,任何通过打开path而有STREAMS管道的访问的进程将仍继续访问这个流,但是后续的path打开会访问在文件系统里的原始文件。
17.2.2 唯一连接
尽管我们能附加一个STREAMS管道的一端到文件系统命名空间,但是如果多个进程想使用命名STREAMS管道和一个服务器通信那么我们仍然有问题。一个
客户的数据将和另一个写到这个管道的数据交叉。即使我们保证客户写少于PIPE_BUF的字节以便写是原子的,然而我们没有方法向单个客户写并保证目的客
户会读到这个消息。当多个客户从相同的管道上读时,我们不能控制哪个会被调度并读取我们发送的东西。
connld STREAMS模块解决了这个问题。在附加一个STREAMS管道到文件系统的一个名字之间,一个服务器进程可以把connld模块推到要被附加的管道那端。这导致下图所示的配置。
在上图,服务器进程已经附加了它的管道的一端到路径/tmp/pipe。我们显示一个虚线来指出一个客户进程正在打开附加的STREAMS管道的过程中。一旦打开完成,我们有下图所示的配置。
客
户进程不会接收它打开的管道的一端的文件描述符。相反,操作系统打开一个新的管道并把一端返回给客户进程作为打开/tmp/pipe的结果。系统把这个新
管道的另一端发送给服务器进程,通过在已有(附加的)管道传递它的文件描述符,导致在客户进程和服务进程之间的唯一连接。我们将在17.4.1节看到使用
STREAMS管道传递文件描述符的机制。
fattach函数在mount系统调用之上建立。这个设置被称为挂载的流。挂载的流和connld模块由Presotto和Richie[1990]为Research UNIX系统开发。这些机制然后被SVR4挑选。
我们现在将开发三个可以在无关进程之间创建唯一连接的函数。这些函数效仿16.4节讨论的面向连接的套接字函数。我们这里使用STREAMS管道作为底下的通信机制,但是我们将在17.3节看到使用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函数,可以被一个服务器用来声明它想在一个熟知名(文件系统里的一些路径名)上监听客户的连接请求。当他们想连接这个服务器时客户将使用这个名字。返回值是STREAMS管道的服务器端。
- #include "apue.h"
- #include <fcntl.h>
- #include <stropts.h>
- /* pipe permissions: user rw, group rw, others rw */
- #define FIFO_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管道,这个函数返回一端给服务器。此外,客户的有效用户ID被存储在uidptr所指的内存里。
- #include "apue.h"
- #include <stropts.h>
- /*
- * Wait for a client connection to arrive, and accept it.
- * We also obtain the client's user ID.
- * Returns 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来连接一个服务器。客户指定的name参数必须和被服务的serv_listen调用监听的名字相同。在返回时,客户得到连接到服务器的一个文件描述符。
- #include "apue.h"
- #include <fcntl.h>
- #include <stropts.h>
- /*
- * Create a client endpoint and connect to a server.
- * Returns fd if all OK, <0 on error.
- */
- int
- cli_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设备,以防服务器还没有启动但路径名仍存在于文件系统里。在17.6节,我们将看到这三个函数如何被使用。
阅读(1583) | 评论(0) | 转发(0) |