《unix高级环境编程》进程间通信——基于 STREAMS 的管道

        前面介绍的《管道进程间通信》是半双工管道,本节基于 STREAMS 的管道是属于全双工的管道,半双工和全双工管道的区别如下:半双工只能在一端进行读或写;全双工可以在某一端同时进行读写;

《unix高级环境编程》进程间通信——基于 STREAMS 的管道_第1张图片

基于 STREAMS 的管道

        基于 STREAMS 的管道是一个全双工的管道,单个 STREAMS 管道就能实现父、子进程之间的双向的数据流操作。下面是基于 STREAMS 的管道两种方式:

《unix高级环境编程》进程间通信——基于 STREAMS 的管道_第2张图片

        基于 STREAMS 管道的内部结构,它包含两个流首,每个流首的写队列(WQ)指向另一个流首的读队列(RQ),写入管道一端的数据被放入另一端的读队列的消息中。 STREAMS 管道是一个流,可将一个 STREAMS 模块压入到该管道的任一端,但是,如果在一端压入了一个模块,那么并不能在另一端弹出模块,若要删除,只能从原压入端删除。下图是基于 STREAMS 管道的内部结构和带模块的 STREAM 管道的内部结构:

《unix高级环境编程》进程间通信——基于 STREAMS 的管道_第3张图片

创建基于 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 管道压入一个模块,即服务器进程将模块压入到要被连接管道的一端。其实现如下图所示:

《unix高级环境编程》进程间通信——基于 STREAMS 的管道_第4张图片

/*
 * 函数功能:创建在无关进程之间的唯一连接;
 * 函数原型:
 */
#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管道)