《Linux – Linux高级编程 – 第二部分 进程与线程》第3章 进程间通信(管道)

3.1管道通信

管道是Linux 中进程间通信的一种方式,它把一个程序的输出直接连接到另一个程序的输入,Linux 的管道主要包括两种:无名管道和有名管道

3.1.1无名管道

无名管道是Linux中管道通信的一种原始方法,他有如下特点:

1)只能用于具有亲缘关系的进程之间的通信(也就是父子进程或兄弟进程之间,用fork()创建的子进程);
2)是一个单工的通信模式,具有固定的读端和写端;
3)管道也可以看成一种特殊的文件,对于它的读写也可是使用普通的read() 、write()等函数,管道文件只做做交互的媒介,不存储数据,必须输入输出都存在才能正常通信,因此它不属于任何文件系统,并且只存在于内存中;(其字节大小为0)

1、无名管道的创建与关闭

无名管道是基于文件描述符的通信方式。当一个管道创建时,它会创建两个文件描述符:fd[0] 、fd[1] 。其中 fd[0] 固定用于读管道,而 fd[1] 固定用于写管道,如下图,这样就构成了一个单向的数据通道:

cwV0aT.png

图1管通信

2、管道创建函数

创建管道可以通过 pipe() 来实现,其语法如下:

函数
需头文件 #include
数原型 nt pipe(int fd[]);
数传入值 d:包含两个元素的整型数组,存放管道文件的文件描述符
数返回值 成功:0;失败:-1

3、管道读写说明

用pipe() 函数创建的管道两端处于一个进程中。由于管道主要是用于不同进程间的通信,通常是先创建一个管道,再调用 fork () 函数创建一个子进程,该子进程会继承父进程所创建的管道。

需要注意的是,无名管道是单工的工作方式,即进程要么只能读管道,要么只能写管道。父子进程虽然都拥有管道的读端和写端,但是只能使用其中一个(例如,可以约定父进程读管道,而子进程写管道)。这样就应该把不使用的读端或写端文件描述符关闭。

cwVyRJ.png

图2

例如:如果将父进程的写端 fd[1] 和子进程的读端 fd[0] 关闭。此时,父子进程之间就建立了一条“子进程写入 父进程读取”的通道。同样,也可以关闭父进程的 fd[0] 和子进程的fd[1] ,这样就可以建立一条“父进程写入子进程读取”的通道。另外,父进程也可以创建 多个子进程,各个子进程都继承了管道的fd[0] 和 fd[1] ,这样就建立子进程之间的数据通道。

4、管道读写注意
1)只有管道的读端存在时,向管道写入数据才有意义,否则,向管道中写入数据的进程将收到内核传来的 SIGPIPE 信号 (通常为Broken Pipea错误)。

2)向管道写入数据时,Linux 将不保证写入的原子性 , 管道缓冲区只要有空间,写进程就会试图向管道写入数据。如果管道缓冲区已满,那么写操作将一直阻塞。

3)父进程在运行时,它们的先后次序必不能保证。为了确保父子进程已经关闭了相应的文件描述符,可在两个进程中调用 sleep() 函数,当然,用互斥和同步会更好;

#include <stdio.h>  
#include <unistd.h>  
#include <string.h>  
#include <stdlib.h>  
int pid,pid1,pid2;  

int main(int argc, const char *argv[])  
{  
    int fd[2];  
    char outpipe[100],inpipe[100];  

    if(pipe(fd) < 0)  
    {  
        perror("pipe error!");  
        return -1;  
    }  
    if((pid1 = fork()) < 0)  
    {  
        perror("fork pid1 error");  
        return -1;  
    }  
    else if(pid1 == 0)  
    {  
        printf("Child1's pid is %d\n",getpid());  
        close(fd[0]);  
        strcpy(outpipe,"Child 1 is sending a message!");  
        if(write(fd[1],outpipe,50) == -1)  
        {  
            perror("Child 1 write to outpipe error");  
            return -1;  
        }  
        exit(0);  
    }  
    if((pid2 = fork()) < 0)  
    {  
        perror("fork pid2 error");  
        return -1;  
    }  
    else if(pid2 == 0)  
    {  
        printf("Child2's pid is %d\n",getpid());  
        close(fd[0]);  
        strcpy(outpipe,"Child 2 is sending a message!");  
        sleep(1);  
        if(write(fd[1],outpipe,50) == -1)  
        {  
            perror("Child 2 write to outpipe error");  
            return -1;  
        }  
        exit(0);  
    }  
    close(fd[1]);  
    pid = wait(NULL);  
    printf("%d process is over!\n",pid);  
    if(read(fd[0],inpipe,50) == -1)  
    {  
        perror("read Child1 pipe error");  
        return -1;  
    }  
    printf("%s\n",inpipe);  

    pid = wait(NULL); //回收第二个结束的子进程  
    printf("%d process is over!\n",pid);  
    if(read(fd[0],inpipe,50) == -1)  
    {  
        perror("read Child1 pipe error");  
        return -1;  
    }  
    printf("%s\n",inpipe);  
    return 0;  
} 

执行结果如下:

cwVgMR.png

3.1.2有名管道

有名管道(FIFO)是对无名管道的一种改进,它具有如下特点:

1)它可以使互不相关的两个进程实现彼此通信;
2)该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程就可以把它当做普通文件一样进行读写操作,使用非常方便;
3)FIFO严格地遵循先进先出规则,对管道及 FIFO 的读总是从开始处返回数据,对它们的写则把数据添加到末尾。有名管道不支持如lseek()等文件定位操作;

有名管道(FIFO)的创建可以使用 mkfifo() 函数,该函数类似文件中的open() 操作,可以指定管道的路径和访问权限 (用户也可以在命令行使用 “mknod <管道名>”来创建有名管道)。

在创建管道成功以后,就可以使用open()、read() 和 write() 这些函数了。与普通文件一样,对于为读而打开的管道可在 open() 中设置 O_RDONLY,对于为写而打开的管道可在 open() 中设置O_WRONLY。

1、对于读进程
缺省情况下,如果当前FIFO内没有数据,读进程将一直阻塞到有数据写入或是FIFO写端都被关闭。

2、对于写进程
只要FIFO有空间,数据就可以被写入。若空间不足,写进程会阻塞,知道数据都写入为止;
mkfifo() 函数语法如下:

表2
所需头文件 #include <sys/types.h>
#include<sys/state.h>
函数原型 int mkfifo(const char *filename, mode_t mode);
参数 mode:管道的访问权限
函数返回值 成功:0;失败:-1
#include <stdio.h>  
#include <stdlib.h>  
#include <sys/stat.h>  
#include <string.h>  
#include <errno.h>  
int main(int argc,char *argv[])  
{  
    if(argc < 2)  
    {  
        printf("Usage:%s <filename>\n",argv[0]);  
        return -1;  
    }  
    if(mkfifo(argv[1],0664) < 0)  
    {  
        perror("mkfifo fails");  
        exit(-1);  
    }  
    return 0;  
} 
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <errno.h>  

#define BUFFER_SIZE 1024  
int main(int argc, const char *argv[])  
{  
    int fd;  
    if(argc < 2)  
    {  
        printf("Usage:%s <filename>\n",argv[0]);  
        return -1;  
    }  
    if((fd = open(argv[1],O_WRONLY)) < 0)  
    {  
        perror("open error");  
        exit(-1);  
    }  
    printf("open fifo %s for writing success!\n",argv[0]);  
    char buffer[BUFFER_SIZE];  
    ssize_t n;  
    while(fgets(buffer,BUFFER_SIZE,stdin))  
    {  
        if((n = write(fd,buffer,strlen(buffer))) == -1)  
        {  
            perror("write fails");  
            break;  
        }  
    }  
    return 0;  
}  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <errno.h>  
#define BUFFER_SIZE 1024  
int main(int argc, const char *argv[])  
{  
    int fd;  
    if(argc < 2)  
    {  
        printf("Usage:%s <filename>\n",argv[0]);  
        return -1;  
    }  
    if((fd = open(argv[1],O_RDONLY)) < 0)  
    {  
        perror("open error");  
        exit(-1);  
    }  
    printf("open fifo %s for reading success!\n",argv[0]);  
    char buffer[BUFFER_SIZE];  
    ssize_t n;  
    while(1)  
    {  
        if((n = read(fd,buffer,BUFFER_SIZE)) == -1)  
        {  
            perror("read fails");  
            return -1;  
        }  
        else if(n == 0)  
        {  
            printf("peer close fifo\n");  
            break;  
        }  
        else  
        {  
            buffer[n] = '\0';  
            printf("read %d bytes from fifo:%s\n",n,buffer);  
        }  
    }  
    return 0;  
}

执行结果如下,创建管道文件:

cwVhdK.png

读端:

cwVoJe.png

写端:(新开一个终端)

cwVTRH.png

这里执行时,可以看到,单独打开读或写,二者会一直阻塞,直到都打开,才会打印第一句话,当写端关闭时,读端也会停止。

Related posts

Leave a Comment