管道和FIFO

UNIX IPC有多种形式,最初使用的便是管道pipe,管道没有名字,又称匿名管道,一般用于有亲缘关系的进程间通信,后来出现了fifo这种管道,它是有名字的,又叫做有名管道,可用于无亲缘关系的进程间通信,这两种管道的数据传输都可以使用我们最熟悉的write、read函数来完成。

1、管道pipe

创建管道使用如下pipe函数:

#include <unistd.h>
int pipe(int pipefd[2]);

pipe函数打开两个文件描述符,pipefd[0]用于读,pipefd[1]用于写,管道创建成功时返回0,否则返回-1,并设置errno。管道提供了一个单向数据流,一般用于父子进程间,两个进程分别关掉它们的读、写文件描述符,如果需要一个双向数据流,则需要创建两个管道,下面举例说明。

// mainpipe.c
/******************************
** A Client-Server Program
** Using fork and two pipes
** Client: father process, input a pathname
** Server: child process, open the pathname and write back to client
******************************/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>

#define MAXLINE (100) // buffer size

// client/server, readfd for read, writefd for write
void client(int readfd, int writefd);
void server(int readfd, int writefd);

int main(int argc, char **argv)
{
    int pipe1[2];
    int pipe2[2];
    pid_t childfd;

    if (-1 == pipe(pipe1) || -1 == pipe(pipe2)) { // create two pipes
        printf("create pipe error: %s", strerror(errno));
        exit(EXIT_FAILURE);
    }

    if (0 == (childfd = fork())) { // child
        // using pipe1[0] for read and pipe2[1] for write
        // so close unused pipe1[1] and pipe2[0]
        if (-1 == close(pipe1[1]) || -1 == close(pipe2[0])) {
            printf("close pipe error: %s", strerror(errno));
            exit(EXIT_FAILURE);
        }

        server(pipe1[0], pipe2[1]); // child is a server
        exit(EXIT_SUCCESS);
    }

    // using pipe2[0] for read and pipe1[1] for write
    // so close unused pipe1[0] and pipe2[1]
    if (-1 == close(pipe1[0]) || -1 == close(pipe2[1])) {
        printf("close pipe error: %s", strerror(errno));
        exit(EXIT_FAILURE);
    }

    client(pipe2[0], pipe1[1]); // father is a client

    if (-1 == waitpid(childfd, NULL, 0)) { // wait for child to terminate
        perror("waitpid error");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

void client(int readfd, int writefd)
{
    size_t len;
    ssize_t n;
    char buff[MAXLINE] = { 0 };

    fgets(buff, MAXLINE, stdin); // using fgets to input from stdin
    len = strlen(buff);
    if ('\n' == buff[len - 1]) {
        --len; // delete newline from fgets
    }

    if (-1 == write(writefd, buff, len)) { // write pathname to pipe
        printf("write error: %s", strerror(errno));
    }

    while (0 < (n = read(readfd, buff, MAXLINE))) { // read data from pipe
        if (-1 == write(STDOUT_FILENO, buff, n)) {
            printf("write error: %s", strerror(errno)); // write data to stdout
        }
    }
}

void server(int readfd, int writefd)
{
    int fd;
    ssize_t n;
    char buff[MAXLINE + 1] = { 0 };

    if (-1 == (n = read(readfd, buff, MAXLINE))) { // read pathname from pipe
        printf("read error: %s", strerror(errno));
    }
    else if (0 == n) {
        perror("end-of-file while reading pathname");
    }
    buff[n] = '\0'; // null terminate

    if (-1 == (fd = open(buff, O_RDONLY))) {
        perror("open error");
        snprintf(buff + n, sizeof(buff) - n, ": can't oepn, %s\n", strerror(errno));
        n = strlen(buff);
        write(writefd, buff, n); // open failed and tell client
    }
    else {
        while (0 < (n = read(fd, buff, MAXLINE))) { // open succeeded and copy file to pipe
            if (-1 == write(writefd, buff, n)) {
                printf("write error: %s", strerror(errno));
            }
        }
        if (-1 == close(fd)) {
            printf("close fd error: %s", strerror(errno));
        }
    }
}

mainpipe.c是一个简单的Client-Server程序,Client通过Server获取文件信息。为了实现双向数据流,即Client输入文件名给Server,而Server又打开这个文件并传输文件内容给Client,这里创建了两个管道。fork之后,子进程为Client,父进程为Server,父子进程分别关闭它们不需要的文件描述符,然后开始通信。

上面提到的管道是一个半双工管道,也就是说一个文件描述符只能用于读,一个文件描述符只能用于写,如果拿某个文件描述符同时进行读写,把它当作全双工管道来用,就会出错,strerror(errno)的输出为“Bad file descriptor”,不过有的系统支持全双工管道的pipe,如SVR4,而socketpair则是具有全双工管道功能的另一个函数。

如果我们在shell终端输入一些支持管道的命令,比如说“ls | sort”,这也是一种单向数据流,半双工管道,把ls的标准输出(一个进程)复制到了sort的标准输入(另一个进程)。

stdio.h中还提供了另一个使用管道的函数popen,它创建一个管道并启动另外一个进程,就好像函数内部封装了pipe和fork一样,该管道也是单双工的,或者读,或者写。

#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);

popen的command参数是一个shell命令,type可以是r或者w,表示读或者写,pclose用于关闭打开的文件流。下面以一个例子说明popen的用法,实现shell的cat命令,所以type参数应该为r。

// mainpopen.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAXLINE (100) // buffer size

int main(int argc, char **argv)
{
    size_t n;
    char buff[MAXLINE];
    char command[MAXLINE];
    FILE *fp;

    fgets(buff, MAXLINE, stdin); // get file name
    n = strlen(buff);
    if ('\n' == buff[n - 1]) {
        --n; // delete newline from fgets
    }
    snprintf(command, sizeof(command), "cat %s", buff); // shell command

    if (NULL == (fp = popen(command, "r"))) { // create pipe for read
        perror("popen error");
        exit(EXIT_FAILURE);
    }

    while(NULL != fgets(buff, MAXLINE, fp)) {
        fputs(buff, stdout);
    }

    pclose(fp); // close from popen

    exit(EXIT_SUCCESS);
}

2、有名管道FIFO

fifo即first in first out,类似于pipe,也是半双工的单向数据流,与pipe不同的是它关联一个路径名,因此也叫做有名管道,可以用于没有亲缘关系的进程间通信。下面是与fifo相关的几个函数。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int mkfifo(const char *pathname, mode_t mode);
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int unlink(const char *pathname);

创建fifo使用mkfifo函数(在shell终端可使用mkfifo命令创建fifo),pathname是这个fifo的路径名,mode为文件权限模式,类似于open函数的第三个参数,受当前进程掩码umask的影响,其真正的权限为(mode & ~umask),创建成功时返回0,失败时返回-1并设置相应的errno。创建失败时,有可能是pathname已经存在了,这时会产生一个EEXIST错误,等同于open函数的flags参数设置了(O_CREAT | O_EXCL)一样,要么创建,要么返回文件已经存在的错误。如果pathname已经存在,可使用open函数来打开并进行IPC通信,open打开时只能是读或者写,即O_RDONLY或者O_WRONLY,而不能是O_RDWR。通信完毕后,从文件系统中删除pathname,使用unlink函数。

下面使用fifo来代替上面Client-Server程序中的pipe,client和server函数不变,只需修改main函数即可。

// mainfifo.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define MAXLINE (100) // buffer size

#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

// client/server, readfd for read, writefd for write
void client(int readfd, int writefd);
void server(int readfd, int writefd);

int main(int argc, char **argv)
{
    int readfd;
    int writefd;
    pid_t childpid;

    if ((0 > mkfifo(FIFO1, FILE_MODE)) && (EEXIST != errno)) { // make fifo, errno can be EEXIST
        printf("mkfifo %s error: %s", FIFO1, strerror(errno));
        exit(EXIT_FAILURE);
    }

    if ((0 > mkfifo(FIFO2, FILE_MODE)) && (EEXIST != errno)) { // make fifo, errno can be EEXIST
        printf("mkfifo %s error: %s", FIFO2, strerror(errno));
        unlink(FIFO1); // remove unuesd fifo
        exit(EXIT_FAILURE);
    }

    if (0 == (childpid = fork())) { // child
        if (0 > (readfd = open(FIFO1, O_RDONLY, 0))) {
            printf("open %s error: %s", FIFO1, strerror(errno));
        }

        if (0 > (writefd = open(FIFO2, O_WRONLY, 0))) {
            printf("open %s error: %s", FIFO2, strerror(errno));
        }

        server(readfd, writefd); // child is a server
        exit(EXIT_SUCCESS);
    }

    if (0 > (writefd = open(FIFO1, O_WRONLY, 0))) {
        printf("open %s error: %s", FIFO1, strerror(errno));
    }

    if (0 > (readfd = open(FIFO2, O_RDONLY, 0))) {
        printf("open %s error: %s", FIFO2, strerror(errno));
    }

    client(readfd, writefd); // father is a client

    if (-1 == waitpid(childpid, NULL, 0)) { // wait for child to terminate
        perror("waitpid error");
    }

    close(readfd);
    close(writefd);

    unlink(FIFO1);
    unlink(FIFO2);

    exit(EXIT_SUCCESS);
}

void client(int readfd, int writefd)
{
    size_t len;
    ssize_t n;
    char buff[MAXLINE] = { 0 };

    fgets(buff, MAXLINE, stdin); // using fgets to input from stdin
    len = strlen(buff);
    if ('\n' == buff[len - 1]) {
        --len; // delete newline from fgets
    }

    if (-1 == write(writefd, buff, len)) { // write pathname to pipe
        printf("write error: %s", strerror(errno));
    }

    while (0 < (n = read(readfd, buff, MAXLINE))) { // read data from pipe
        if (-1 == write(STDOUT_FILENO, buff, n)) {
            printf("write error: %s", strerror(errno)); // write data to stdout
        }
    }
}

void server(int readfd, int writefd)
{
    int fd;
    ssize_t n;
    char buff[MAXLINE + 1] = { 0 };

    if (-1 == (n = read(readfd, buff, MAXLINE))) { // read pathname from pipe
        printf("read error: %s", strerror(errno));
    }
    else if (0 == n) {
        perror("end-of-file while reading pathname");
    }
    buff[n] = '\0'; // null terminate

    if (-1 == (fd = open(buff, O_RDONLY))) {
        perror("open error");
        snprintf(buff + n, sizeof(buff) - n, ": can't oepn, %s\n", strerror(errno));
        n = strlen(buff);
        write(writefd, buff, n); // open failed and tell client
    }
    else {
        while (0 < (n = read(fd, buff, MAXLINE))) { // open succeeded and copy file to pipe
            if (-1 == write(writefd, buff, n)) {
                printf("write error: %s", strerror(errno));
            }
        }
        if (-1 == close(fd)) {
            printf("close fd error: %s", strerror(errno));
        }
    }
}

上面例子中的fifo应用是在父子进程间进行的,下面再对其进行修改,在两个无亲缘关系的进程间完成。

// server_main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define MAXLINE (100) // buffer size

#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

// server, readfd for read, writefd for write
void server(int readfd, int writefd);

int main(int argc, char **argv)
{
    int readfd;
    int writefd;

    if ((0 > mkfifo(FIFO1, FILE_MODE)) && (EEXIST != errno)) { // make fifo, errno can be EEXIST
        printf("mkfifo %s error: %s", FIFO1, strerror(errno));
        exit(EXIT_FAILURE);
    }

    if ((0 > mkfifo(FIFO2, FILE_MODE)) && (EEXIST != errno)) { // make fifo, errno can be EEXIST
        printf("mkfifo %s error: %s", FIFO2, strerror(errno));
        unlink(FIFO1); // remove unuesd fifo
        exit(EXIT_FAILURE);
    }

    if (0 > (readfd = open(FIFO1, O_RDONLY, 0))) {
        printf("open %s error: %s", FIFO1, strerror(errno));
    }

    if (0 > (writefd = open(FIFO2, O_WRONLY, 0))) {
        printf("open %s error: %s", FIFO2, strerror(errno));
    }

    server(readfd, writefd); // child is a server

    exit(EXIT_SUCCESS);
}

void server(int readfd, int writefd)
{
    int fd;
    ssize_t n;
    char buff[MAXLINE + 1] = { 0 };

    if (-1 == (n = read(readfd, buff, MAXLINE))) { // read pathname from pipe
        printf("read error: %s", strerror(errno));
    }
    else if (0 == n) {
        perror("end-of-file while reading pathname");
    }
    buff[n] = '\0'; // null terminate

    if (-1 == (fd = open(buff, O_RDONLY))) {
        perror("open error");
        snprintf(buff + n, sizeof(buff) - n, ": can't oepn, %s\n", strerror(errno));
        n = strlen(buff);
        write(writefd, buff, n); // open failed and tell client
    }
    else {
        while (0 < (n = read(fd, buff, MAXLINE))) { // open succeeded and copy file to pipe
            if (-1 == write(writefd, buff, n)) {
                printf("write error: %s", strerror(errno));
            }
        }
        if (-1 == close(fd)) {
            printf("close fd error: %s", strerror(errno));
        }
    }
}
// client_main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define MAXLINE (100) // buffer size

#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

// client readfd for read, writefd for write
void client(int readfd, int writefd);

int main(int argc, char **argv)
{
    int readfd;
    int writefd;

    if (0 > (writefd = open(FIFO1, O_WRONLY, 0))) {
        printf("open %s error: %s", FIFO1, strerror(errno));
    }

    if (0 > (readfd = open(FIFO2, O_RDONLY, 0))) {
        printf("open %s error: %s", FIFO2, strerror(errno));
    }

    client(readfd, writefd); // father is a client

    close(readfd);
    close(writefd);

    unlink(FIFO1);
    unlink(FIFO2);

    exit(EXIT_SUCCESS);
}

void client(int readfd, int writefd)
{
    size_t len;
    ssize_t n;
    char buff[MAXLINE] = { 0 };

    fgets(buff, MAXLINE, stdin); // using fgets to input from stdin
    len = strlen(buff);
    if ('\n' == buff[len - 1]) {
        --len; // delete newline from fgets
    }

    if (-1 == write(writefd, buff, len)) { // write pathname to pipe
        printf("write error: %s", strerror(errno));
    }

    while (0 < (n = read(readfd, buff, MAXLINE))) { // read data from pipe
        if (-1 == write(STDOUT_FILENO, buff, n)) {
            printf("write error: %s", strerror(errno)); // write data to stdout
        }
    }
}

然后就可以在shell终端测试了:

$ gcc -o server server_main.c
$ gcc -o client client_main.c
$ ./sever &
$ ./client

3、管道和FIFO的使用注意事项

上面的例子只是简单的介绍了pipe和fifo的用法,其实在使用过程中,还有一些东西是需要注意的。

阻塞与否——

设置非阻塞时,可以通过open函数的O_NONBLOCK标志实现,也可以通过fcntl函数完成,使用fcntl时一般先通过F_GETFL命令获取文件当前状态flags,然后再将(flags |= O_NONBLOCK)通过G_SETFL命令设置非阻塞,否则可能会出问题。

OPEN_MAX——

一个进程在任意时刻打开文件描述符的最大个数,其值可通过sysconf(_SC_OPEN_MAX)函数查询,使用setrlimit函数来修改,在shell终端也可以使用”getconf OPEN_MAX”命令来查询,通过ulimit命令修改。

PIPE_BUF——

可原子地写往pipe或fifo的最大数据量,其值可使用pathconf(path, _PC_PIPE_BUF)或fpathconf函数获得,在shell终端使用”getconf PIPE_BUF ”命令也可以查询某个path的PIPE_BUF。

你可能感兴趣的:(linux,ipc,pipe,fifo)