消息传递:管道和FIFO

发布时间 2023-12-01 11:50:41作者: eiSouthBoy

一、简介

管道是没有名字的,管道创建的资源由内核管理,单个程序中不同进程通过管道描述符fd进行通信,对于程序和程序之间是无法通信的。

FIFO是有名字的(也称为 有名管道),每一个FIFO都有一个文件与之关联,但仅限于同一主机程序与程序之间通信,无法通过在NFS上创建FIFO通信。

二、管道

所有管道都是半双工的即单向的,只提供一个方向的数据流。管道的经典用途是为两个不同的进程(一个父进程,一个子进程)提供进程间通信手段。

案例示意图:

pipe_example.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>

#define MAX_LINE 1024

void client(int read_fd, int write_fd)
{
    size_t len;
    ssize_t n;
    char buff[MAX_LINE];

    /* read pathname for user input */
    fgets(buff, MAX_LINE, stdin);
    len = strlen(buff);
    if (buff[len - 1] == '\n')
        len--;
    /* write pathname to IPC channel */
    write(write_fd, buff, len);

    /* read for IPC channel, write to standard output */
    while ((n = read(read_fd, buff, MAX_LINE)) > 0)
    {
        write(STDOUT_FILENO, buff, n);
    }
}

void server(int read_fd, int write_fd)
{
    int fd;
    ssize_t n;
    char buff[MAX_LINE];

    /* read pathname for IPC channel. if not any message arrived, block at here. */
    if ((n = read(read_fd, buff, MAX_LINE)) == 0)
    {
        printf("error: end-of-file while reading pathname\n");
        exit(EXIT_FAILURE);
    }
    buff[n] = '\0'; // null terminate pathname
    if ((fd = open(buff, O_RDONLY)) < 0) // error must tell client
    {
        snprintf(buff + n, sizeof(buff) - n, ":can't open, %s\n", strerror(errno));
        n = strlen(buff);
        write(write_fd, buff, n);
    }
    else
    {
        /* read file , then write to pipe channel.
         * if the last character of file is EOF, break while, exit server.
         */
        while ((n = read(fd, buff, MAX_LINE)) > 0)
        {
            write(write_fd, buff, n);
        }
        close(fd);
    }
}

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

    /* create 2 pipes */
    if (pipe(pipe1) != 0)
    {
        printf("failed to create pipe1\n");
        exit(EXIT_FAILURE);
    }
    if (pipe(pipe2) != 0)
    {
        printf("failed to create pipe2\n");
        exit(EXIT_FAILURE);
    }

    child_pid = fork();
    if (child_pid == 0) /* child */
    {
        close(pipe1[1]);
        close(pipe2[0]);
        server(pipe1[0], pipe2[1]);
        exit(0);
    }
    else if (child_pid > 0) /* parent */
    {
        close(pipe1[0]);
        close(pipe2[1]);
        client(pipe2[0], pipe1[1]);
        waitpid(child_pid, NULL, 0);
    }
    else
    {
        printf("failed to fork process\n");
        exit(EXIT_FAILURE);
    }

    exit (0);
}

运行测试:

三、FIFO

3.1 进程间通信(FIFO)

对于管道案例进行改写为fifo,修改部分不涉及 server(int, int) and client(int, int)

程序(FIFO)示意图:

fifo_example.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>

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

/* default permission for new files.  */
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)


void client(int read_fd, int write_fd)
{
    size_t len;
    ssize_t n;
    char buff[MAX_LINE];

    /* read pathname for user input */
    fgets(buff, MAX_LINE, stdin);
    len = strlen(buff);
    if (buff[len - 1] == '\n')
        len--;
    /* write pathname to IPC channel */
    write(write_fd, buff, len);

    /* read for IPC channel, write to standard output */
    while ((n = read(read_fd, buff, MAX_LINE)) > 0)
    {
        write(STDOUT_FILENO, buff, n);
    }
}

void server(int read_fd, int write_fd)
{
    int fd;
    ssize_t n;
    char buff[MAX_LINE];

    /* read pathname for IPC channel. if not any message arrived, block at here. */
    if ((n = read(read_fd, buff, MAX_LINE)) == 0)
    {
        printf("error: end-of-file while reading pathname\n");
        exit(EXIT_FAILURE);
    }
    buff[n] = '\0'; // null terminate pathname
    if ((fd = open(buff, O_RDONLY)) < 0) // error must tell client
    {
        snprintf(buff + n, sizeof(buff) - n, ":can't open, %s\n", strerror(errno));
        n = strlen(buff);
        write(write_fd, buff, n);
    }
    else
    {
        /* read file , then write to pipe channel.
         * if the last character of file is EOF, break while, exit server.
         */
        while ((n = read(fd, buff, MAX_LINE)) > 0)
        {
            write(write_fd, buff, n);
        }
        close(fd);
    }
}

int main(int argc, char **argv)
{
    int read_fd, write_fd;
    pid_t child_pid;

    /* create 2 FIFOs. */
    if ((mkfifo(FIFO1, FILE_MODE)) < 0 && (errno != EEXIST))
    {
        printf("error: can't create %s\n", FIFO1);
        exit(EXIT_FAILURE);
    }
    if ((mkfifo(FIFO2, FILE_MODE)) < 0 && (errno != EEXIST))
    {
        unlink(FIFO1);
        printf("error: can't create %s\n", FIFO2);
        exit(EXIT_FAILURE);
    }

    child_pid = fork();
    if (child_pid == 0) /* child */
    {
        read_fd = open(FIFO1, O_RDONLY, 0);
        write_fd = open(FIFO2, O_WRONLY, 0);
        server(read_fd, write_fd);
        
        exit(0);
    }
    else if (child_pid > 0) /* parent */
    {
        
        write_fd = open(FIFO1, O_WRONLY, 0);
        read_fd = open(FIFO2, O_RDONLY, 0);
        client(read_fd, write_fd);
        waitpid(child_pid, NULL, 0);
        close(read_fd);
        close(write_fd);
    }
    else
    {
        printf("failed to fork process\n");
        exit(EXIT_FAILURE);
    }

    unlink(FIFO1);
    unlink(FIFO2);
    exit (0);
}

运行测试:

3.2 程序间通信(FIFO)

FIFO更多使用在程序和程序间,例如程序A往FIFO写数据,程序B往FIFO读数据。

fifo_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>

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

/* default permission for new files.  */
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

void client(int read_fd, int write_fd)
{
    size_t len;
    ssize_t n;
    char buff[MAX_LINE];

    /* read pathname for user input */
    fgets(buff, MAX_LINE, stdin);
    len = strlen(buff);
    if (buff[len - 1] == '\n')
        len--;
    /* write pathname to IPC channel */
    write(write_fd, buff, len);

    /* read for IPC channel, write to standard output */
    while ((n = read(read_fd, buff, MAX_LINE)) > 0)
    {
        write(STDOUT_FILENO, buff, n);
    }
}

int main(int argc, char **argv)
{
    int read_fd, write_fd;

    write_fd = open(FIFO1, O_WRONLY, 0);
    read_fd = open(FIFO2, O_RDONLY, 0);
    client(read_fd, write_fd);

    close(read_fd);
    close(write_fd);
    unlink(FIFO1);
    unlink(FIFO2);

    exit(0);
}

fifo_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>

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

/* default permission for new files.  */
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

void server(int read_fd, int write_fd)
{
    int fd;
    ssize_t n;
    char buff[MAX_LINE];

    /* read pathname for IPC channel. if not any message arrived, block at here. */
    if ((n = read(read_fd, buff, MAX_LINE)) == 0)
    {
        printf("error: end-of-file while reading pathname\n");
        exit(EXIT_FAILURE);
    }
    buff[n] = '\0'; // null terminate pathname
    if ((fd = open(buff, O_RDONLY)) < 0) // error must tell client
    {
        snprintf(buff + n, sizeof(buff) - n, ":can't open, %s\n", strerror(errno));
        n = strlen(buff);
        write(write_fd, buff, n);
    }
    else
    {
        /* read file , then write to pipe channel.
         * if the last character of file is EOF, break while, exit server.
         */
        while ((n = read(fd, buff, MAX_LINE)) > 0)
        {
            write(write_fd, buff, n);
        }
        close(fd);
    }
}

int main(int argc, char **argv)
{
    int read_fd, write_fd;
    if ((mkfifo(FIFO1, FILE_MODE)) < 0 && (errno != EEXIST))
    {
        printf("error: can't create %s\n", FIFO1);
        exit(EXIT_FAILURE);
    }
    if ((mkfifo(FIFO2, FILE_MODE)) < 0 && (errno != EEXIST))
    {
        unlink(FIFO1);
        printf("error: can't create %s\n", FIFO2);
        exit(EXIT_FAILURE);
    }

    read_fd = open(FIFO1, O_RDONLY, 0);
    write_fd = open(FIFO2, O_WRONLY, 0);
    server(read_fd, write_fd);

    exit(0);
}

运行测试:
先启动 fifo_server程序,再启动 fifo_client程序。

3.3 单服务器-多客户端通信(FIFO)

FIFO的真正优势表现在服务器可以是一个长期运行的进程(例如 守护进程),而且与其客户可以无情缘关系。

如下例子:客户端通过 众所周知的路径名创建的FIFO 发送一个请求,服务器解析请求并通过创建一个与客户端pid关联的FIFO,将响应写入这个FIFO。客户端发送请求后,打开 一个与自己pid关联的FIFO ,等待服务器的响应。

服务端代码
server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>

#define MAX_LINE 1024
#define SERV_FIFO "/tmp/fifo.serv"

/* default permission for new files.  */
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

int read_cnt = 0;
char *read_ptr = NULL;
char read_buf[MAX_LINE] = {0};

static ssize_t my_read(int fd, char *ptr)
{
    if (read_cnt <= 0)
    {
    again:
        if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0)
        {
            if (errno == EINTR)
                goto again;
            return -1;
        }
        else if (read_cnt == 0)
        {
            return 0;
        }
        read_ptr = read_buf;
    }

    read_cnt--;
    *ptr = *read_ptr++;
    return 1;
}

/*
 * @return if the return value greater than or equal to 0, result is successful.
 * if 0, read EOF. if -1, error occur.  
*/
ssize_t readline(int fd, void *vptr, size_t max_len)
{
   ssize_t i = 0; 
   ssize_t ret = 0;
   char ch = '\0'; 
   char *ptr = NULL;

   ptr = (char *)vptr;
   for (i = 1; i < max_len; i++)
   {
        if ((ret = my_read(fd, &ch)) == 1)
        {
            *ptr++ = ch;
            if (ch == '\n') // newline is stored, return
                break;
        }
        else if (ret == 0) // EOF, n - 1 bytes were read
        {
            *ptr = 0;
            return (i - 1);
        }
        else
        {
            return -1; // error, errno set by read()
        } 
   }

   *ptr = 0; // null terninate
    return i;
}

int main(int argc, char **argv)
{
    int read_fifo, write_fifo, dummy_fd, fd;
    char *ptr, buff[MAX_LINE + 1], fifo_name[MAX_LINE];
    pid_t pid;
    ssize_t n;

    /* create server's well-known FIFO, server read-only */
    if ((mkfifo(SERV_FIFO, FILE_MODE)) < 0 && errno != EEXIST)
    {
        printf("error: can't create %s\n", SERV_FIFO);
        exit(EXIT_FAILURE);
    }
    read_fifo = open(SERV_FIFO, O_RDONLY, 0);
    dummy_fd = open(SERV_FIFO, O_WRONLY, 0); // never use

    /* receive request from client, and respond to client */
    while ((n = readline(read_fifo, buff, MAX_LINE)) > 0)
    {
        printf("server read (%s) fifo msg:%s", SERV_FIFO, buff);
        if (buff[n - 1] == '\n')
            n--;
        buff[n] = '\0';
       
        if ((ptr = strchr(buff, ' ')) == NULL)
        {
            printf("bogus request: %s\n", buff);
            continue;
        }
        *ptr++ = 0; // null terminate PID, ptr = pathname
        pid = atol(buff);
        snprintf(fifo_name, sizeof(fifo_name), "/tmp/fifo.%ld", (long)pid);
        if ((write_fifo = open(fifo_name, O_WRONLY, 0)) < 0)
        {
            printf("error: can't open %s\n", fifo_name);
            continue;
        }
        if ((fd = (open(ptr, O_NDELAY))) < 0)
        {
            /* error must tell client */
            snprintf(buff + n, sizeof(buff) - n, ": can't open, %s\n", strerror(errno));
            n = strlen(ptr);
            write(write_fifo, ptr, n);
            close(write_fifo);
        }
        else
        {
            /* open succeeded: copy file to FIFO */
            while ((n = read(fd, buff, MAX_LINE)) > 0)
            {
                write(write_fifo, buff, n);
            }
            close(fd);
            close(write_fifo);
        }
    }

    exit(0);
}

客户端代码
client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>

#define MAX_LINE 1024
#define SERV_FIFO "/tmp/fifo.serv"

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

int main(int argc, char **argv)
{
    int read_fifo, write_info;
    size_t len;
    ssize_t n;
    char *ptr, fifo_name[MAX_LINE], buff[MAX_LINE];
    pid_t pid;

    /* create FIFO with our PID as part of name */
    pid = getpid();
    snprintf(fifo_name, sizeof(fifo_name), "/tmp/fifo.%ld", (long)pid);
    if ((mkfifo(fifo_name, FILE_MODE) < 0) && (errno != EEXIST))
    {
        printf("error: can't create %s\n", fifo_name);
        exit(EXIT_FAILURE);
    }
    snprintf(buff, sizeof(buff), "%ld ", (long)pid);
    len = strlen(buff);
    ptr = buff + len;
    if (write_info = open(SERV_FIFO, O_WRONLY, 0) < 0)
    {
        printf("error: can't open %s\n", SERV_FIFO);
        unlink(fifo_name);
        exit(EXIT_FAILURE);
    }
    /* read pathname from stdin, and append buff string.
     * read string from stdin include '\n'
     */
    fgets(ptr, MAX_LINE - len, stdin);
    len = strlen(buff);
    write(write_info, buff, len);
    printf("client write (%s)fifo msg:%s", SERV_FIFO, buff);
    /* open our FIFO, blocks until server opens for writing */
    read_fifo = open(fifo_name, O_RDONLY, 0);
    while ((n = read(read_fifo, buff, MAX_LINE)) > 0)
    {
        write(STDOUT_FILENO, buff, n);
    }
    close(read_fifo);
    unlink(fifo_name);

    exit(0);
}

运行测试:

若服务端未启动,客户端启动时,无法发送请求。

启动服务端后,再启动客户端

通过 shell命令(echo) 给服务端发送请求,然后读取响应内容。

四、管道和FIFO限制

系统对于管道和FIFO的唯一限制为:

  • OPEN_MAX : 一个进程在任意时刻打开的最大描述符数(Posix要求至少为16)
  • PIPE_BUF : 可原子地写往管道或FIFO的最大数据量(Posix要求至少为512)