进程间通信之管道(pipe)和命名管道(FIFO)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/daaikuaichuan/article/details/82827994

一、管道(pipe)

1、管道的定义和特点

  管道是一种两个进程间进行单向通信的机制。因为管道传递数据的单向性,管道又称为半双工管道。管道的这一特点决定了器使用的局限性。管道是Linux支持的最初Unix IPC形式之一,具有以下特点:

  • 数据只能由一个进程流向另一个进程(其中一个读管道,一个写管道);如果要进行双工通信,需要建 立两个管道;
  • 管道只能用于父子进程或者兄弟进程间通信。,也就是说管道只能用于具有亲缘关系的进程间通信

2、管道的命令

command1 | command2 | command3

  操作符是:”|”,它只能处理经由前面一个指令传出的正确输出信息,对错误信息信息没有直接处理能力。然后,传递给下一个命令,作为标准的输入。
  举个例子,在shell中输入命令:ls -l | grep string,我们知道ls命令(其实也是一个进程)会把当前目录中的文件都列出来,但是它不会直接输出,而是把本来要输出到屏幕上的数据通过管道输出到grep这个进程中,作为grep这个进程的输入,然后这个进程对输入的信息进行筛选,把存在string的信息的字符串(以行为单位)打印在屏幕上。

3、管道的使用

#include <unistd.h>  
int pipe(int filedes[2]);   //成功返回0,失败返回-1

  pipe函数用来创建一个管道,fd是传出参数,用于保存返回的两个文件描述符,该文件描述符用于标识管道的两端,fd[0]只能由于读,fd[1]只能用于写。所以管道只能保证单向的数据通信。
  一般管道的使用方式都是:父进程创建一个管道,然后fork产生一个子进程,由于子进程拥有父进程的副本,所以父子进程可以通过管道进程通信。这种使用方式如下图所示:

对于从父进程到子进程的管道,父进程关闭读端(fd[0]),子进程关闭写端(fd[1]);对于从子进程到父进程的管道,子进程关闭读端(fd[0]),父进程关闭写端(fd[1])。

  当管道的一端被关闭后,会出现下面的几种情况:

1. 当读一个写端被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束;如果写端没有被关闭,但是没有数据,则读端读完数据后阻塞;

2. 当写一个读端被关闭的管道时,则产生信号SIGPIPE,write返回-1,errno设置为EPIPE;如果读端没有被关闭,写端写满数据后,则写端阻塞。

4、管道的demo

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>

int main()
{
    int fd[2]; // 定义文件描述符
    pid_t pid;
    char str[1024] = "hello\n";
    char buf[1024];
    if (pipe(fd) < 0) // 创建管道,成功返回0,失败返回-1
    {
        perror("pipe");
        exit(1);
    }
    pid = fork(); // 创建一个子进程
    // 功能:父写子读
    if (pid > 0) // 父进程
    {
        close(fd[0]); // 父进程关闭读端
        sleep(2);
        write(fd[1], str, strlen(str)); // 向管道里写数据
        wait(NULL); // 回收子进程
    }
    else if (pid == 0) // 子进程
    {
        close(fd[1]); // 子进程关闭写端
        int len = read(fd[0], buf, sizeof(buf)); // 从管道里读数据
        write(STDOUT_FILENO, buf, len); // 把读到的数据写到标准输出
    }
    else // 创建子进程失败
    {
        perror("fork");
        exit(1);
    }
    return 0;
}

二、popen和pclose函数

  作为关于管道的一个实例,就是标准I/O函数库提供的popen函数,该函数创建一个管道,并fork一个子进程,该子进程根据popen传入的参数,关闭管道的对应端,然后执行传入的shell命令,然后等待终止。
  调用进程和fork的子进程之间形成一个管道。调用进程和执行shell命令的子进程之间的管道通信是通过popen返回的FILE*来间接的实现的,调用进程通过标准文件I/O来写入或读取管道。

#include <stdio.h>  
// 成功返回标准文件I/O指针,失败返回NULL  
FILE *popen(const char *command, const char *type); 
// 成功返回shell的终止状态,失败返回-1  
int pclose(FILE *stream);   
  • command:该传入参数是一个shell命令行,这个命令是通过shell处理的;
  • type:该参数决定调用进程对要执行的command的处理,type有如下两种情况:type = “r”,调用进程将读取command执行后的标准输出,该标准输出通过返回的FILE*来操作;type = “w”,调用进程将写command执行过程中的标准输入。
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    FILE *fpr = NULL, *fpw = NULL;
    char buf[256];
    int ret;
    // 执行完这行代码,标准输出就装满,这里这个标准输出标记为out1,
    // 管道指向out1,fpr指向管道的读端。执行这句代码,会一直去读取标准输出,
    // 若标准输出为空,则会阻塞,直到标准输出不为空,执行命令后又会去指
    // 读取标准输出继续执行。这里这个标准输入标记为in2。
    // 管道指向int2,fpw指向管道的写端。
    fpr = popen("cat /etc/group", "r"); 
    fpw = popen("grep root", "w");       
    // 从out1中读取256个字节数据,存放在buf中。
    while ((ret = fread(buf, 1, sizeof(buf), fpr)) > 0)  
    {
    	// 将buf的数据写到int2(此时gerp命令一直在获取int2,直到进行退出)。
        fwrite(buf, 1, ret, fpw); 
    }
    pclose(fpr);
    pclose(fpw);
    return 0;
}

【popen的原理及优缺点】:

  当调用popen运行一个新进程时,它首先启动shell,然后将command参数传递给它。

  • 优点:可以使用shell来分析命令字符串,启动非常复杂的shell命令;
  • 缺点:不仅要启动一个新进程,还要启动一个shell,效率会比较低。

三、命名管道(FIFO)

1、命名管道的定义和特点

  POSIX标准中的FIFO又名有名管道或命名管道。我们知道前面讲述的POSIX标准中管道是没有名称的,所以它的最大劣势是只能用于具有亲缘关系的进程间的通信。FIFO最大的特性就是每个FIFO都有一个路径名与之相关联,从而允许无亲缘关系的任意两个进程间通过FIFO进行通信。所以,FIFO的两个特性:

  • 和管道一样,FIFO仅提供半双工的数据通信,即只支持单向的数据流;
  • 和管道不同的是,FIFO可以支持任意两个进程间的通信。

2、命名管道的使用

  下面是FIFO的接口定义:

#include <sys/types.h>  
#include <sys/stat.h>  
/ /成功则返回0,失败返回-1 
int mkfifo(const char *pathname, mode_t mode);  
  • pathname:一个Linux路径名,它是FIFO的名字。即每个FIFO与一个路径名相对应;
  • mode:指定的文件权限位,类似于open函数的第三个参数。即创建该FIFO时,指定用户的访问权限,有以下值:S_IRUSR,S_IWUSR,S_IRGRP,S_IWGRP,S_IROTH,S_IWOTH。

  mkfifo函数默认指定O_CREAT | O_EXECL方式创建FIFO,如果创建成功,直接返回0。如果FIFO已经存在,则创建失败,会返回-1并且errno置为EEXIST。对于其他错误,则置响应的errno值;

  当创建一个FIFO后,它必须以只读方式打开或者只写方式打开,所以可以用open函数,当然也可以使用标准的文件I/O打开函数,例如fopen来打开。由于FIFO是半双工的,所以不能够同时打开来读和写。

  其实一般的文件I/O函数,如read,write,close,unlink都可用于FIFO。对于管道和FIFO的write操作总是会向末尾添加数据,而对他们的read则总是会从开头数据,所以不能对管道和FIFO中间的数据进行操作,因此对管道和FIFO使用lseek函数,是错误的,会返回ESPIPE错误。

  mkfifo的一般使用方式是:通过mkfifo创建FIFO,然后调用open,以读或者写的方式之一打开FIFO,然后进行数据通信。

3、命名管道的demo

// FIFOwrite.c
#include <stdio.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
  
int main(int argc, char *argv[])  
{  
    int fd;  
    int ret;      
    ret = mkfifo("my_fifo", 0666); // 创建命名管道  
    if(ret != 0)  
    {
        perror("mkfifo");  
    } 
    fd = open("my_fifo", O_WRONLY); // 等着只读  
    if(fd < 0)  
    {  
        perror("open fifo");  
    }  
    char send[100] = "Hello World";  
    write(fd, send, strlen(send));  // 写数据  
    printf("write to my_fifo buf=%s\n",send);  
    while(1); // 阻塞,保证读写进程保持着通信过程
    close(fd);
    return 0;  
}
// FIFOread.c
#include <stdio.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
  
int main(int argc, char *argv[])  
{  
    int fd;  
    int ret;      
    ret = mkfifo("my_fifo", 0666); // 创建命名管道  
    if(ret != 0)  
    {  
        perror("mkfifo");  
    }  
    fd = open("my_fifo", O_RDONLY); // 等着只写  
    if(fd < 0)  
    {  
        perror("open fifo");  
    }  
    while(1)  
    {  
        char recv[100] = {0};
        read(fd, recv, sizeof(recv)); // 读数据  
        printf("read from my_fifo buf=[%s]\n", recv);  
        sleep(1);  
    }
    close(fd);
    return 0;  
} 

四、总结

1. 管道是一个环形队列缓冲区,允许两个进程以生产者消费者模型进程通信。是一个先进先出(FIFO)队列,由一个进程写,而由另一个进程读。

2. 管道在创建时获得一个固定大小的字节数。当一个进程试图往管道中写时,如果有足够的空间,则写请求立即被执行,否则该进程被阻塞。如果一个进程试图读取的字节数多于当前管道中的字节数,也将被阻塞。

3. 操作系统强制实行互斥,只能有一个进程可以访问管道

4. 只有有血缘关系(父子关系)的进程才可以共享匿名管道,不相关的进程只能共享命名管道

5. 命名管道的用途主要有:(1)shell命名使用FIFO将数据从一条管道传送到另一条时,无须创建中间临时文件;(2)在客户进程和服务器进程间传送数据。

参考:https://www.cnblogs.com/wuchanming/p/4381737.html
https://blog.csdn.net/skc361/article/details/38124959
https://blog.csdn.net/a1414345/article/details/57472205

猜你喜欢

转载自blog.csdn.net/daaikuaichuan/article/details/82827994