进程间通讯之管道(PIPE和FIFO)

管道的定义

管道是一种两个进程间单向通信的机制,因为管道传递数据的单向性,所以我们也称他为半双工管道。管道分为无名管道与命名管道

管道的特点

  • 数据只能从一个进流出到一个进程流入(一个读端,一个写端);如果需要进行双工通信,那么就需要两个管道
  • 无名管道,只能在父子进程之间进行通讯,只能用于有亲缘关系的进程之间通讯。

管道的命令

cmd1|cmd2

 操作符是:”|”,它只能处理经由前面一个指令传出的正确输出信息,对错误信息信息没有直接处理能力。然后,传递给下一个命令,作为标准的输入。

管道的使用

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

pipe函数用于创建一个无名管道,数组是传出参数,用于保存两个返回的文件描述符,两个返回的文件描述符以一种特殊的方式连接在一起。写在fd[1]里的数据可以从fd[0]里读出来。数据读取采用的是先进先出的原则。

一般无名管道的使用方式都是,父进程创建一个管道,然后fork一个子进程,由于子进程拥有父进程的副本,所以父子进程之间可以通过管道里通讯。

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

当管道的一端被关闭的时候,会出现以下的情况

  • 当读一个写端被关闭的管道时,在所有数据读完以后,read返回为0,表示文件结束,如果写端没有被关闭,那么读端便会读完数据后阻塞。
  • 当写一个读端被关闭的管道时,则产生信号SIGPIPE,write返回为-1,error置为EPIPE,如果读端没有被关闭,那么写端在数据写满以后便会进入阻塞。

无名管道代码如下:

#include<stdio.h>
#include<assert.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

int main()
{
  int fd[2]={0};//定义文件描述符
  pipe(fd);
  pid_t pid =fork();//创建子进程
  if(pid==0)//如果是子进程
   {
    char buff[128]={0};
    close(fd[1]);//关闭写端
    while(1)
    {
        read(fd[0],buff,127); //从管道中读取数据
        printf("child:: buff=%s",buff);
    }
    close(fd[0]);//关闭读端
   }
   else
   {
       close(fd[0]);//关闭读端
       char cuff[128]={0};
       sleep(1);
       while(1)
       {
           fgets(cuff,127,stdin);//标准输入
           write(fd[1],cuff,6);//将数据写到管道
        }
        close(fd[1]);//关闭写端
    }
    
}

Popen和Pclose函数

最简单的在两个进程之间传递数据的方法就是使用popen和pclose函数了

他们的原型如下:

#include <stdio.h>  
// 成功返回标准文件I/O指针,失败返回NULL  
FILE *popen(const char *command, const char *open_mod); 
// 成功返回shell的终止状态,失败返回-1  
int pclose(FILE *stream_close);   

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

    command字符串是要运行的 程序名和相应的参数       open_mod必须是“r”或者“w”。如果是“r”,被调用程序的输出就可以被调用程序使用。如果是“w”,调用程序的输入就可以被调用程序使用。

#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;
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

popen函数的优缺点:

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

命名管道(FIFO)

命名管道定义及特点

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

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

命名管道的使用

#include <sys/types.h>  
#include <sys/stat.h>  
/ /成功则返回0,失败返回-1 
int mkfifo(const char *filename, mode_t mode);  
int mknod(const char *filename, mode_t mode | S_IFIFO,(dve_t)0); 
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==
  • filename:一个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,然后进行数据通信。

命名管道代码如下:

// 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;  
} 

  当然 ,如果我们是在同一目录下用管道连接两个进程,我们还可以直接使用shell命令mkfifo filename 创建一个命名管道,然后在程序中open打开就可以了。

总结

  • 管道是一个环形队列缓冲区,允许两个进程以生产者消费者模型进程通信。是一个先进先出(FIFO)队列,由一个进程写,而由另一个进程读。
  • 管道在创建时获得一个固定大小的字节数。当一个进程试图往管道中写时,如果有足够的空间,则写请求立即被执行,否则该进程被阻塞。如果一个进程试图读取的字节数多于当前管道中的字节数,也将被阻塞。
  • 操作系统强制实行互斥,只能有一个进程可以访问管道。
  • 只有有血缘关系(父子关系)的进程才可以共享匿名管道,不相关的进程只能共享命名管道。
  • 命名管道的用途主要有:(1)shell命名使用FIFO将数据从一条管道传送到另一条时,无须创建中间临时文件;(2)在客户进程和服务器进程间传送数据。

 

参考:https://blog.csdn.net/daaikuaichuan/article/details/82827994

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值