基于STREAMS的管道(简称STREAMS管道,STREAMS pipe)是一个双向(全双工)管道。单个STREAMS管道就能向父、子进程提供双向的数据流。

http://www.cnblogs.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.cnblogs.com/nufangrensheng/p/3561379.html)中的协同进程实例。程序清单17-1是新的main函数。add2协同进程与程序清单15-8(http://www.cnblogs.com/nufangrensheng/p/3561379.html)中的相同。本程序调用了创建单个STREAMS管道的新函数s_pipe(见下个实例)。

程序清单17-1 用STREAMS管道驱动add2过滤进程的程序

#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)
{
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 void
sig_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].
*/
int
s_pipe(int fd[2])
{
return(pipe(fd));
}

注意,POSIX.1允许实现支持全双工管道。对于这些实现,filedes[0]和filedes[1]以读/写方式打开(http://www.cnblogs.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.cnblogs.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.cnblogs.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函数连接至服务器进程。客户进程指定的参数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.
*/ 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设备进行了二次检验,以防止服务器进程没被启动而路径名仍存在于文件系统中。

本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/

高级进程间通信之基于STREAMS的管道的更多相关文章

  1. APUE读书笔记-第17章-高级进程间通信

    17.1 引言 *两种高级IPC:基于STREAMS的管道(STREAMS-based pipe)以及UNIX域套接字(UNIX domain socket)可以在进程间传送打开文件描述符.服务进程可 ...

  2. 高级I/O之STREAMS

    http://en.wikipedia.org/wiki/STREAMS STREAMS(流)是系统V提供的构造内核设备驱动程序和网络协议包的一种通用方法,对STREAMS进行讨论的目的是为了理解系统 ...

  3. Unix环境高级编程(十八)高级进程间通信

    本章主要介绍了基于STREAM的管道和UNIX域套接字,这些IPC可以在进程间传送打开文件描述符.服务进程可以使用它们的打开文件描述符与指定的名字相关联,客户进程可以使用这些名字与服务器进程通信. 1 ...

  4. Linux进程间通信--进程,信号,管道,消息队列,信号量,共享内存

    Linux进程间通信--进程,信号,管道,消息队列,信号量,共享内存 参考:<linux编程从入门到精通>,<Linux C程序设计大全>,<unix环境高级编程> ...

  5. 高级进程间通信之UNIX域套接字

    UNIX域套接字用于在同一台机器上运行的进程之间的通信.虽然因特网域套接字可用于同一目的,但UNIX域套接字的效率更高.UNIX域套接字仅仅复制数据:它们并不执行协议处理,不需要添加或删除网络报头,无 ...

  6. Linux进程间通信IPC学习笔记之管道

    基础知识: 管道是最初的Unix IPC形式,可追溯到1973年的Unix第3版.使用其应注意两点: 1)没有名字: 2)用于共同祖先间的进程通信: 3)读写操作用read和write函数 #incl ...

  7. apue学习笔记(第十七章 高级进程间通信)

    本章介绍一种高级IPC---UNIX域套接字机制,并说明它的应用方法 UNIX域套接字 UNIX域套接字用于在同一台计算机上运行的进程(无关进程)之间的(全双工)通信.相比于因特网套接字,UNIX域套 ...

  8. spring对bean的高级装配之基于@Conditional条件化装配

    上篇介绍了如何基于profile来条件化创建bean,spring会根据profile的激活状态来进行创建;这篇介绍如何基于spring4.0引入的@Conditional和Condition接口来更 ...

  9. 持续集成高级篇之基于win32-openssh搭建jenkins混合集群(一)

    系列目录 前面的demo我们使用的都是只有一个windows主节点的的jenkins,实际生产环境中,一个节点往往是不能满足需求的.比如,.net项目要使用windows节点构建,java项目如果部署 ...

随机推荐

  1. codeforces 260 div2 B题

    打表发现规律,对4取模为0的结果为4,否则为0,因此只需要判断输入的数据是不是被4整出即可,数据最大可能是100000位的整数,判断能否被4整出不能直接去判断,只需要判断最后两位(如果有)或一位能否被 ...

  2. Github上如何取消fork别人的repository

    在Github上如果看到有很不错的项目和作品,一般我们可以进行三种操作:那就是watch, star和fork. Watch也就是关注该repo的动态,star则类似于Facebook和Twitter ...

  3. 使用rpmbuild来创建自己的RPM

    1. 进行创建必须的目录 在6.2的版本中,路径发生了变化,必须在此路径中,否则必须要修改配置文件. 2. 创建脚本文件 编写一个简单的脚本,然后将脚本进行打包为tar.gz格式的压缩文件,并且将其放 ...

  4. C++ 为什么要用覆盖(学习笔记)

    长篇大论这里就不说了,举个例子class fruit{public: void func() { printf("fruit\n"); } virtual void vfunc() ...

  5. 利用Javascript获取当前日期的农历日期

    来源:http://www.ido321.com/926.html JavaScript代码 1: /*设置农历日期*/ 2: var CalendarData=new Array(100); 3: ...

  6. Windwos Server 2008: 当网卡有多个IP地址时,如何指定缺省地址?

    这实际是一个当应用向外发起连接时,协议栈对源IP地址的选择问题.如果你的应用没有显式绑定一个本地地址,协议栈会选择一个"最佳"的本地地址来使用. 从 Vista 之后这个选择策略发 ...

  7. 应用数据存储到sdcard上一定要规范,android4.4.2有新规范

    如果你的android设备有内部存储空间,即通常所说的机身存储(这就是指主要外部存储),那么你从外部插入SD卡就是一个二级外部存储设备. 最新的Android 4.4系统中,外置存储卡(SD卡)被称为 ...

  8. Hibernate之QBC查询与本地SQL查询

    1. QBC查询:     QBC 查询就是通过使用Hibernate提供的QueryByCriteria API 来查询对象,这种API封装了SQL语句的动态拼装,对查询提供了更加面向对象的功能接口 ...

  9. 新浪云sae 邮件服务 quicksend()

    <?php header("Content-Type: text/html;charset=utf-8"); $mail = new SaeMail(); $form_Con ...

  10. 汇总:Linux下svn命令大全

    svn(subversion)是近年来崛起的版本管理工具,svn服务器有2种运行方式:独立服务器和借助apache.2种方式各有利弊.不管是那种方式,都需要使用各种命令来实现.在本文中,haohtml ...