前面介绍的《管道进程间通信》是半双工管道,本节基于 STREAMS 的管道是属于全双工的管道,半双工和全双工管道的区别如下:半双工只能在一端进行读或写;全双工可以在某一端同时进行读写;
基于 STREAMS 的管道是一个全双工的管道,单个 STREAMS 管道就能实现父、子进程之间的双向的数据流操作。下面是基于 STREAMS 的管道两种方式:
基于 STREAMS 管道的内部结构,它包含两个流首,每个流首的写队列(WQ)指向另一个流首的读队列(RQ),写入管道一端的数据被放入另一端的读队列的消息中。 STREAMS 管道是一个流,可将一个 STREAMS 模块压入到该管道的任一端,但是,如果在一端压入了一个模块,那么并不能在另一端弹出模块,若要删除,只能从原压入端删除。下图是基于 STREAMS 管道的内部结构和带模块的 STREAM 管道的内部结构:
创建基于 STREAMS 管道只是对普通管道进行接口实现,其实现如下:
#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)); }
命名的 STREAMS 管道和 FIFO 管道一样克服了管道的局限性,使其可以在没有亲缘关系的进程间通信,命名的 STREAMS 管道机制通过一种途径,使得进程可以给予管道一个文件系统中的名字,使其能够实现双向通信,避免了 FIFO 管道的单向通信。为了使进程给予管道一个文件系统中的名字,可以调用函数 fattach 使进程给予 STREAMS 管道一个文件系统中的名字,其定义如下:
/* 命名STREAM 管道 */ /* * 函数功能:使进程给予STREAM管道一个文件系统中的名字; * 返回值:若成功则返回0,若出错则返回-1; * 函数原型: */ #include <stropts.h> int fattach(int filedes, const char *path); /* * 说明: * path必须是引用一个现有的文件,且对该文件具有写权限; */ /* * 函数功能:撤销STREAM管道与一个文件系统中的名字的关联; * 返回值:若成功则返回0,若出错则返回-1; * 函数原型: */ #include <stropts.h> int fdetach(const char *path);一旦 STREAMS 管道连接到文件系统名字空间,那么原来使用该名字的底层文件就不再是可访问的。打开该名字的任一进程将能访问相应管道,而不是访问原先的文件。在调用 fattach 之前打开底层文件的任一进程可以继续访问该文件。确实,一般而言,这些进程并不知道该名字现在引用了另外一个文件。
在调用 fdetach 函数之后,先前依靠打开 path 而能访问 STREAMS 管道的进程仍可继续访问该管道,但是在此之后打开 path 的进程将访问驻留在文件系统中的底层文件。
将 STREAMS 管道的一端连接到文件系统的名字空间后,如果多个进程都使用命名 STREAMS 管道与服务器进程通信时,会出现通信混乱。为了解决多进程访问出现的问题,在 STREAMS 管道压入一个模块,即服务器进程将模块压入到要被连接管道的一端。其实现如下图所示:
/* * 函数功能:创建在无关进程之间的唯一连接; * 函数原型: */ #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管道的服务器进程端; * * 服务器进程调用serv_accept函数等待客户进程连接请求的到达,当一个请求到达时,系统自动创建一个新的STREAMS管道, * 该函数向服务器进程返回该STREAMS管道的一端,客户进程的有效用户ID存放在uidptr所指向的存储区中; * * 客户端进程调用cli_conn函数连接至服务器进程,客户端进程指定的参数name必须和服务器进程调用serv_listen函数时所用的参数name相同; * 该函数返回时,客户端进程得到连接至服务器进程的文件描述符; * */
#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 */ }
#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 */ }
#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); }
《UNIX高级环境编程》