Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1745020
  • 博文数量: 438
  • 博客积分: 9799
  • 博客等级: 中将
  • 技术积分: 6092
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-25 17:25
文章分类

全部博文(438)

文章存档

2019年(1)

2013年(8)

2012年(429)

分类: 系统运维

2012-04-03 13:35:40

(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域套接字版本。)



  1. #include "apue.h"
  2. static void sig_pipe(int); /* our signal handler */
  3. int
  4. main(void)
  5. {
  6.     int n;
  7.     int fd[2];
  8.     pid_t pid;
  9.     char line[MAXLINE];
  10.     if (signal(SIGPIPE, sig_pipe) == SIG_ERR)
  11.         err_sys("signal error");
  12.     if (s_pipe(fd) < 0) /* need only a single stream pipe */
  13.         err_sys("pipe error");
  14.     if ((pid = fork()) < 0) {
  15.         err_sys("fork error");
  16.     } else if (pid > 0) { /* parent */
  17.         close(fd[1]);
  18.         while (fgets(line, MAXLINE, stdin) != NULL) {
  19.             n = strlen(line);
  20.             if (write(fd[0], line, n) != n)
  21.                 err_sys("write error to pipe");
  22.             if ((n = read(fd[0], line, MAXLINE)) < 0)
  23.                 err_sys("read error from pipe");
  24.             if (n == 0) {
  25.                 err_msg("child closed pipe");
  26.                 break;
  27.             }
  28.             line[n] = 0; /* null terminate */
  29.             if (fputs(line, stdout) == EOF)
  30.                 err_sys("fputs error");
  31.         }
  32.         if (ferror(stdin))
  33.             err_sys("fgets error on stdin");
  34.         exit(0);
  35.     } else { /* child */
  36.         close(fd[0]);
  37.         if (fd[1] != STDIN_FILENO &&
  38.                 dup2(fd[1], STDIN_FILENO) != STDIN_FILENO)
  39.             err_sys("dup2 error to stdin");
  40.         if (fd[1] != STDOUT_FILENO &&
  41.                 dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO)
  42.             err_sys("dup2 error to stdout");
  43.         if (execl("./add2", "add2", (char *)0) < 0)
  44.             err_sys("execl error");
  45.     }
  46.     exit(0);
  47. }
  48. static void
  49. sig_pipe(int signo)
  50. {
  51.     printf("SIGPIPE caught\n");
  52.     exit(1);
  53. }

父进程只使用了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函数。



  1. #include "apue.h"
  2. /*
  3. * Returns a STREAMS-based pipe, with the two file descriptors
  4. * returned in fd[0] and fd[1].
  5. */
  6. int
  7. s_pipe(int fd[2])
  8. {
  9.     return(pipe(fd));
  10. }

17.2.1 命名STREAMS管道


通常,管道可以只在相关进程之间使用:子进程从父进程继承管道。在15.5节,我们看到了无关进程可以使用FIFO来通信,但是它只提供了单向的通信路径。STREAMS路径为进程提供一种方法在文件系统里给一个管道一个名字。这绕过了处理单向FIFO的问题。


我们可以使用fattach函数来在文件系统给一个STREAMS管道一个名字。



  1. #include <stropts.h>

  2. int fattach(int filedes, const char *path);

  3. 成功返回0,错误返回-1。


path参数必须引用一个已有的文件,调用进程必须拥有这个文件,或有对它有写的权限,或使用超级用户特权运行。


一旦STREAMS管道被附加到文件系统命名空间上时,底下的文件是不可访问的。任何打开这个名字的进程将得到这个管道的访问,不是底下的文件。事实上,这些进程通常会不知道这个名字现在引用到一个不同的文件。


管道只有一端被附加到文件系统的一个名字。另一端用来和打开这个附加的文件名的进程通信。即使它可以附加任何类型的STREAMS文件到文件系统的一个名字,fattach函数普遍用来给一个STREAMS管道一个名字。


一个进程可以调用fdetach来撤消一个STREAMS文件和文件系统里的名字之间的关联。



  1. #include <stropts.h>

  2. int fdetach(const cha *path);

  3. 成功返回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域套接字的这些函数的另一种的实现。



  1. #include "apue.h"

  2. int serv_listen(const char *name);

  3. 成功返回要监听的文件描述符,错误返回负值。

  4. int serv_accept(int listenfd, uid_t *uidptr);

  5. 成功返回新的文件描述符,错误返回负值。

  6. int cli_conn(const char *name);

  7. 成功返回文件描述符,错误返回负值。

下面的代码实现了serv_listen函数,可以被一个服务器用来声明它想在一个熟知名(文件系统里的一些路径名)上监听客户的连接请求。当他们想连接这个服务器时客户将使用这个名字。返回值是STREAMS管道的服务器端。



  1. #include "apue.h"
  2. #include <fcntl.h>
  3. #include <stropts.h>
  4. /* pipe permissions: user rw, group rw, others rw */
  5. #define FIFO_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
  6. /*
  7.    * Establish an endpoint to listen for connect requests.
  8.    * Returns fd if all OK, <0 on error
  9.    */
  10. int
  11. serv_listen(const char *name)
  12. {
  13.     int tempfd;
  14.     int fd[2];
  15.     /*
  16.        * Create a file: mount point for fattach().
  17.        */
  18.     unlink(name);
  19.     if ((tempfd = creat(name, FIFO_MODE)) < 0)
  20.         return(-1);
  21.     if (close(tempfd) < 0)
  22.         return(-2);
  23.     if (pipe(fd) < 0)
  24.         return(-3);
  25.     /*
  26.        * Push connld & fattach() on fd[1].
  27.        */
  28.     if (ioctl(fd[1], I_PUSH, "connld") < 0) {
  29.         close(fd[0]);
  30.         close(fd[1]);
  31.         return(-4);
  32.     }
  33.     if (fattach(fd[1], name) < 0) {
  34.         close(fd[0]);
  35.         close(fd[1]);
  36.         return(-5);
  37.     }
  38.     close(fd[1]); /* fattach holds this end open */
  39.     return(fd[0]); /* fd[0] is where client connections arrive */
  40. }

下面的代码是serv_accept函数,被一个服务器用来等待一个客户的连接请求的到达。当某个到达时,系统自动创建一个新的STREAMS管道,这个函数返回一端给服务器。此外,客户的有效用户ID被存储在uidptr所指的内存里。


  1. #include "apue.h"
  2. #include <stropts.h>
  3. /*
  4.    * Wait for a client connection to arrive, and accept it.
  5.    * We also obtain the client's user ID.
  6.    * Returns new fd if all OK, <0 on error.
  7.    */
  8. int
  9. serv_accept(int listenfd, uid_t *uidptr)
  10. {
  11.     struct strrecvfd recvfd;
  12.     if (ioctl(listenfd, I_RECVFD, &recvfd) < 0)
  13.         return(-1); /* could be EINTR if signal caught */
  14.     if (uidptr != NULL)
  15.         *uidptr = recvfd.uid; /* effective uid of caller */
  16.     return(recvfd.fd); /* return the new descriptor */
  17. }



一个客户调用下面代码里的cli_conn来连接一个服务器。客户指定的name参数必须和被服务的serv_listen调用监听的名字相同。在返回时,客户得到连接到服务器的一个文件描述符。


  1. #include "apue.h"
  2. #include <fcntl.h>
  3. #include <stropts.h>
  4. /*
  5.    * Create a client endpoint and connect to a server.
  6.    * Returns fd if all OK, <0 on error.
  7.    */
  8. int
  9. cli_conn(const char *name)
  10. {
  11.     int fd;
  12.     /* open the mounted stream */
  13.     if ((fd = open(name, O_RDWR)) < 0)
  14.         return(-1);
  15.     if (isastream(fd) == 0) {
  16.         close(fd);
  17.         return(-2);
  18.     }
  19.     return(fd);
  20. }


我们仔细核查了返回的描述符引用到一个STREAMS设备,以防服务器还没有启动但路径名仍存在于文件系统里。在17.6节,我们将看到这三个函数如何被使用。
阅读(1535) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~