基于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. HDU-3280 Equal Sum Partitions

    http://acm.hdu.edu.cn/showproblem.php?pid=3280 用了简单的枚举. Equal Sum Partitions Time Limit: 2000/1000 M ...

  2. 《Python 学习手册4th》 第十一章 赋值、表达式和打印

    ''' 时间: 9月5日 - 9月30日 要求: 1. 书本内容总结归纳,整理在博客园笔记上传 2. 完成所有课后习题 注:“#” 后加的是备注内容 (每天看42页内容,可以保证月底看完此书) “重点 ...

  3. 【原】Storm环境搭建

    2.Storm环境搭建 单机 ... 集群 ... 搭建Storm开发环境 搭建Storm开发环境主要概括为以下两步: 1.下载Storm发行稳定版,然后解压,最后把解压后的bin/文件所在目录添加到 ...

  4. poj2396 Budget(有源汇上下界可行流)

    [题目链接] http://poj.org/problem?id=2396 [题意] 知道一个矩阵的行列和,且知道一些格子的限制条件,问一个可行的方案. [思路] 设行为X点,列为Y点,构图:连边(s ...

  5. Lucene学习笔记:一,全文检索的基本原理

    一.总论 根据http://lucene.apache.org/java/docs/index.html定义: Lucene是一个高效的,基于Java的全文检索库. 所以在了解Lucene之前要费一番 ...

  6. <Chapter 2>2-2-1.用户偏好模式(The User Preferences Pattern)

    在这个模块中我们要创建的应用是一个简单的钟.当一个用户访问这个网站时,这个应用会更具服务器的系统时间显示当前的时间.默认情况下,这个应用使用标准国际时间(UTC)时区显示当前时间.用户可以使用Goog ...

  7. <Stackoverflow> 声望和节制

    什么是声望(reputation)?我是怎样获得(或失去)它的? 声望是一种粗略的测量,用来表示社区对你的信任度.通过让别人相信你知道自己正在讨论什么来获得.对网站的基本使用,包括问一个问题,回答,建 ...

  8. 如何用十条命令在一分钟内检查Linux服务器性能

    “如果你的Linux服务器突然负载暴增,报警短信快发爆你的手机,如何在最短时间内找出Linux性能问题所在?来看Netflix性能工程团队的这篇博文,看它们通过十条命令在一分钟内对机器性能问题进行诊断 ...

  9. Codeforces Round #260 (Div. 1) A. Boredom (简单dp)

    题目链接:http://codeforces.com/problemset/problem/455/A 给你n个数,要是其中取一个大小为x的数,那x+1和x-1都不能取了,问你最后取完最大的和是多少. ...

  10. Serializable 序列化为文件

    package test; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundExcept ...