前面介绍的《管道进程间通信》是半双工管道,本节基于 STREAMS 的管道是属于全双工的管道,半双工和全双工管道的区别如下:半双工只能在一端进行读或写;全双工可以在某一端同时进行读写;
基于 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 管道
命名的 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相同;
* 该函数返回时,客户端进程得到连接至服务器进程的文件描述符;
*
*/
上面三个函数的具体实现如下:这些函数可以套用在前面介绍《 基于 socket 的编程》的框架中。
#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高级环境编程》
深入理解基于STREAMS的管道通信

本文详细阐述了基于STREAMS的管道通信机制,包括半双工与全双工管道的区别,基于STREAMS的全双工管道实现,命名STREAMS管道如何克服管道局限性,以及唯一连接机制解决多进程访问问题。
1709

被折叠的 条评论
为什么被折叠?



