UNIX网络编程之管道与FIFO

Posted Dandelion_gong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UNIX网络编程之管道与FIFO相关的知识,希望对你有一定的参考价值。

管道是最初的Unix IPC形式,它们的最大局限是没有名字,所以,管道只能用于有亲缘关系的进程只见使用。之后,慢慢随着FIFO的加入,这点才有所改观。FIFO也成为又名管道。管道和FIFO的共同点就是它们都是通过read和write函数进行访问的。

管道:
管道时有pipe函数创建,提供一个单路数据流。也就是说,所有的管道都是半双工。
管道创建方法:
#include <unistd.h>
int pipe(int fd[2]);

该函数返回两个文件描述符:fd[0] (用来打开读)、fd[1] (用来打开写)。管道只是它形象的叫法,它的本质实际上就是文件。

单个进程中的管道模式:
这里写图片描述
一般管道很少只在单个进程中进行使用。管道最常用于两个不同但有亲缘关系的进程(一个父进程,一个子进程。或两个有共同祖先的进程)中,提供进程间的通信。

进程间通信模式:
这里写图片描述
只要有亲缘关系的两个进程都可以用管道进行通信。这里我们用父进程和子进程进行介绍。
首先,我们有主进程创建一个管道后,调用fork()函数派生一个自身的副本。此时主进程将成为父进程,它的副本将成为子进程。完成这些预备操作后,父进程将关闭相应管道的读出端(fd[0]),子进程将关闭该管道的写入端(fd[1])。这样父进程可以通过write函数写入数据,而子进程通过read函数读出数据【必须先写入数据才能读出】。

进程间双向数据流:
这里写图片描述
双向数据与进程间单向数据流十分相似。只是它是创建了两个管道。父进程关闭了管道1的读端口(fd1[0])和管道2的写端口(fd2[1]),子进程则恰好相反,它关闭的是管道2的读端口(fd2[0])和管道1的写端口(fd1[1])。这样,两个管道可以保证数据的双向流动。父进程由管道2进行读数据,由管道1进行写数据,而子进程则由管道2写数据,由管道1读数据。

下面是进程间双向数据流的实现代码:
步骤:
(1)、创建管道1和管道2(利用pipe函数)
(2)、fork一个子进程
(3)、父进程关闭管道1的读端口(fd1[0])和管道2的写端口(fd2[1])
(4)、子进程关闭管道1的写端口(fd1[1])和管道2的读端口(fd2[1])

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
using namespace std;

int main()
{
    //分别定义一个字符串数组记录父进程和子进程所传数据,最后一个为NULL
    char* parent_talk[] = {"Hello",
                           "can you tell me current data and time?",
                           "I have to go, Bye",
                          NULL};
    char* child_talk[] = {"Hi",
                          "No problem:",
                         "Bye.",NULL};

    int fd1[2], fd2[2];  //创建两个管道

    //检测管道是否创建成功,如果创建成功会返回0,否则返回-1
    if(pipe(fd1)<0)     
    {
        printf("create pipe1 error.\\n");
        exit(1);
    }
    if(pipe(fd2)<0)
    {
        printf("create pipe2 error.\\n");
        exit(1);
    }

    pid_t pid;
    pid = fork(); //fork一个子进程,并将子进程的id号符给父进程的pid
    if(pid == 0)   //子进程没有自己的子进程,所以子进程pid = 0
    {
        char buffer[256];
        //关闭子进程需要关闭的端口
        close(fd1[1]);
        close(fd2[0]);

        int i=0;
        char *child = child_talk[i];
        while(child != NULL)
        {   //只要子进程字符串数组不为NULL,就说明通信为及未结束
            //从管道1中读出数据,并打印出来
            read(fd1[0],buffer,256);
            printf("Parent:>%s\\n",buffer);
            //给管道2中写入数据
            if(i == 1)
            {
                time_t t;
                time(&t);
                sprintf(buffer,"%s%s",child,ctime(&t));
                write(fd2[1],buffer,strlen(buffer)+1);
            }else{
                write(fd2[1],child,strlen(child)+1);
            }
            i++;
            child = child_talk[i];
        }
        //数据传输结束后,关闭所有端口
        close(fd1[0]);
        close(fd2[1]);
    }
    //父进程
    else if(pid > 0)
    {
        char buffer[256];
        close(fd1[0]);
        close(fd2[1]);
        int i = 0;
        char *parent = parent_talk[i];
        //父进程的字符串数组中数据不为NULL时,继续写入数据
        while(parent != NULL)
        { 
            //将数据写入管道1
            write(fd1[1],parent,strlen(parent)+1);
            //从管道2中读出子进程发送的数据
            read(fd2[0],buffer,256);
            printf("Child:>%s\\n",buffer);
            i++;
            parent = parent_talk[i];
        }
        //通信结束后,关闭所有端口
        close(fd1[1]);
        close(fd2[0]);

        //等待子进程结束,然后回收它的空间,防止它成为孤儿进程
        int status;
        wait(&status);
    }
    //如果pid不满足上述条件,则说明fork子进程失败
    else
    {
        printf("Create child process error!\\n");
    }
    return 0;
}

FIFO(有名管道):
FIFO即先进先出,每个FIFO有一个路径名与之相关联,所以它可以实现无亲缘关系的进程之间进行通信访问同一个FIFO。FIFO又称为又名管道。与管道不同的是,FIFO时有mkfifo函数创建,创建成功则返回0,失败则返回1。

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

其中pathname是一个普通的路径名,它将是该FIFO的名字;mode则指定文件权限位,一般使用的权限位参数为:O_CREAT|O_EXCL,意思为,它要么创建一个新的FIFO,要么返回一个EEXIST(已存在错误)。在使用mkfifo函数时,它会检测是否返回EEXIST错误,如果返回该错误,则直接调用open函数打开即可。
在创建出一个FIFO后,必须打开读或写,但不能同时打开读和写,因为它和管道一样也是半双工。
对于管道和FIFO而言,write是往末尾添加数据,而read则是从头部返回数据。

用两个FIFO实现客户-服务器:
这里写图片描述
它的原理和用管道实现双向数据流相似,它是用FIFO1来进行服务器给客户端发送数据,而用FIFO2来实现客户端给服务器传送数据。

实现程序:
utili.h :头文件

#pragma once

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include <sys/stat.h>
using namespace std;
//创建两个路径名(mkfifo函数中pathname参数)
const char *write_fifo_name = "write_fifo";
const char *read_fifo_name = "read_fifo";

ser.cpp:服务端程序:

#include"utili.h"

int main()
{
    int write_fd;
    int read_fd;
    //创建一个write_fifo_name的FIFO                                              
    int res = mkfifo(write_fifo_name,O_CREAT|O_EXCL|S_IRUSR|S_IWUSR);
    if(res == -1)   //如果返回值为-1,则创建FIFO失败
    {
        printf("make write fifo error.\\n");
        exit(1);
    }
    //创建成功后,以只写方式打开write_fifo_name管道
    write_fd = open(write_fifo_name,O_WRONLY);
    //如果返回-1则表明打开失败
    if(write_fd == -1)
    {
        printf("open write fifo error.\\n");
        unlink(write_fifo_name);
        exit(1);
    }
    //打开成功后等待客户端
    printf("Wait Client Connect......\\n");
    //以只读方式打开read_fifo_name,并等待客户端的连接
    while((read_fd = open(read_fifo_name, O_RDONLY)) == -1)
    {
        sleep(1);
    }
    printf("Client Connect Ok.\\n");
    定义一个发送数组和接收数组
    char sendbuf[256];
    char recvbuf[256];
    while(1)
    {
        //服务器从write_fifo_name写入数据
        printf("Ser:>");
        scanf("%s",sendbuf);
        write(write_fd,sendbuf,strlen(sendbuf)+1);
        //服务器从read_fifo_name读出来自客户端的数据
        read(read_fd,recvbuf,256);
        printf("Cli:>%s\\n",recvbuf);
    }

    return 0;
}

cli.cpp:客户端程序

#include"utili.h"

int main()
{
    int write_fd, read_fd;
//创建一个名为read_fifo_name的FIFO,如果创建失败则返回-1,成功则返回0    
    int res = mkfifo(read_fifo_name, O_CREAT|O_EXCL|S_IRUSR|S_IWUSR);
    if(res == -1)
    {
        printf("make read fifo error.\\n");
        exit(1);
    }
    //客户端以只读的形式打开write_fifo_name
    read_fd = open(write_fifo_name, O_RDONLY);
    if(read_fd == -1)  //如果返回值为-1则表明打开失败
    {
        printf("Server Error.\\n");
        unlink(read_fifo_name);
        exit(1);
    }
    //客户端以只写方式打开read_fifo_name
    write_fd = open(read_fifo_name,O_WRONLY);
    if(write_fd == -1)  //如果返回值为-1,则打开失败
    {
        printf("Client Connect Server Error.\\n");
        exit(1);
    }
    //定义两个字符串数组,分别用来存放客户端发送数据和接收的数据
    char sendbuf[256];
    char recvbuf[256];
    while(1)
    {
        //客户端通过write_fifo_name来读取来自服务器的数据
        read(read_fd,recvbuf,256);
        printf("Ser:>%s\\n",recvbuf);
        //客户端通过write_fifo_name写入数据
        printf("Cli:>");
        scanf("%s",sendbuf);
        write(write_fd,sendbuf,strlen(sendbuf)+1);
    }
    return 0;
}

以上是关于UNIX网络编程之管道与FIFO的主要内容,如果未能解决你的问题,请参考以下文章

Linux系统编程——进程间通信:命名管道(FIFO)

Linux进程间通信之管道(pipe)命名管道(FIFO)与信号(Signal)

Linux IPC之管道和FIFO

多进程编程之进程间通信

管道和FIFO

简述Linux进程间通信之命名管道FIFO