Linux进程间通信(一)之无名管道(PIPE)和有名管道(FIFO)

来源:互联网 发布:笑傲江湖 知乎 编辑:程序博客网 时间:2024/06/08 05:09

何为进程间通信

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。简单说就是进程之间可以相互发送数据。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。Socket用在网络编程中。

管道

管道通常指无名管道,是 UNIX 系统IPC最古老的形式。
特点有:
1、它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
2、它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间),实现依赖父子进程文件共享。
3、它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
函数原型

#include <unistd.h>int pipe(int filedes[2]);

由参数filedes返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。
单个进程中的管道几乎没有任何用处。通常,调用pipe的进程接着调用fork,这样就创建了从父进程到子进程(或反向)的IPC通道。
父子进程都有读端和写端,子进程的是从父进程复制过来的。
进程复制的时候复制了PCB、文件结构体,不止拷贝了文件描述符。

进程间通信之管道0.png

进程间通信之管道1.png
调用fork之后做什么取决于我们想要有的数据流的方向。对于从父进程到子进程的管道,父进程关闭管道的读端(fd[0]),子进程则关闭写端(fd[1])。
进程间通信之管道2.png
举例:
例1:只有一个进程,读写管道文件

#include <stdio.h>#include <stdlib.h>#include <assert.h>#include <string.h>#include <unistd.h>int main(){    int fd[2];    pipe(fd);    write(fd[1],"hello world",12);    sleep(2);    char buff[128];    int n = read(fd[0],buff,127);    printf("read:%s\n",buff);    close(fd[0]);    close(fd[1]);}

输出结果是:
进程间通信之管道9.png

例2:创建由父进程到子进程的管道,父进程写入helloworld,子进程读取数据到buf,然后打印输出。

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <assert.h>#include <unistd.h>int main(){    int fd[2];    pipe(fd);//fd[0]是读,fd[1]是写    if(fork() != 0)    {        close(fd[0]);        write(fd[1],"helloworld",10);    }    else    {        close(fd[1]);        char buf[128] = {0};        read(fd[0],buf,127);        printf("%s\n",buf);    }    return 0;}

输出结果如下:
进程间通信之管道8.png

例3:使用管道文件,创建由父进程到子进程的管道,父进程循环输入数据,子进程循环读取数据,当写端输入end时,父子线程都结束。

#include <stdio.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#include <assert.h>int main(){    int fd[2];    pipe(fd);    pid_t pid = fork();    if(pid == 0)    {        close(fd[1]);        char buff[128] = {0};        int n = 0;        while((n = read(fd[0],buff,127)) > 0)        {            printf("child read:%s\n",buff);        }        close(fd[0]);    }    else    {        close(fd[0]);        while(1)        {            char buff[128] = {0};            printf("input:\n");            fgets(buff,128,stdin);            if(strncmp(buff,"end",3)==0)            {                break;            }            write(fd[1],buff,127);        }        close(fd[1]);    }    exit(0);}

注意:
(1)当读一个写端已被关闭的管道是,在所有数据都被读取后,read返回0,以指示达到文件结束处。管道的写端彻底关闭(父子进程的写端都得关闭,否则会有进程处于未关闭状态,还在等待写),读端返回一个0,父子进程都得关闭。
(2)如果写一个读端已被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从处理程序返回,则write返回-1,errno设置为EPIPE。

有名管道

有名管道也叫命名管道,在文件系统目录中存在一个管道文件。
管道文件仅仅是文件系统中的标示,并不在磁盘上占据空间。在使用时,在内存上开辟空间,作为两个进程数据交互的通道。
管道文件的创建:
1) 在shell中使用mkfifo 命令
mkfifo filename
2) mkfifo 函数 (在代码中使用其创建管道文件)
函数原型:

#include <sys/types.h>#include <sys/stat.h>int mkfifo(const char *filename,mode_t mode);

使用方式:
1)使用open函数打开管道文件
如果一个进程以只读(只写)打开,那么这个进程会被阻塞到open,直到另一个进程以只写(只读)或者读写。
2)使用read函数读取内容
read读取普通文件,read不会阻塞。而read读取管道文件,read会阻塞运行,直到管道中有数据或者所有的写端关闭。
3)使用write函数发送内容,使用close函数关闭打开的文件。
举例:
首先通过命令创建管道文件:fifo

mkfifo fifo

进程间通信之管道3.png
例1:一个进程写,一个进程读。
写端:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <assert.h>#include <fcntl.h>#include <signal.h>//信号处理函数。void fun(int sig){    printf("sig == %d\n",sig);}int main(){    signal(SIGPIPE,fun);    int fd = open("fifo",O_WRONLY); // int fd = open("fifo",O_RDWR);    assert(fd != -1);    printf("fd = %d\n",fd);    char buff[128] = {0};    while(1)    {        printf("input:\n");        fgets(buff,128,stdin);        write(fd,buff,strlen(buff));        if(strncmp(buff,"end",3)==0)        {            break;        }    }    close(fd);    exit(0);}

读端:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <assert.h>#include <fcntl.h>int main(){    int fd = open("fifo",O_RDONLY);    assert(fd != -1);    printf("fd = %d\n",fd);    char buff[128] = {0};//  while(1)//  {//      int n = read(fd,buff,127);//127是期望要读的字符个数//      if(strncmp(buff,"end",3)==0)//      {//          break;//      }//      printf("read:%s\n",buff);//      printf("n = %d\n",n);//  }    int n = 0;    while((n = read(fd,buff,127))>0)    {        printf("read:(n = %d)%s\n",n,buff);        //将buff中的数据清空        memset(buff,0,128);    }    close(fd);    exit(0);}

输出结果:
(1)正常写入和读取,输入end结束读写。

进程间通信之管道4.png

(2)写端彻底关闭、读端read返回0,也会关闭。

进程间通信之管道5.png

(3)管道的读端关闭,当写端继续写入数据时,会产生SIGPIPE信号,修改默认响应方式,故信号被捕获之后执行信号处理函数fun。

进程间通信之管道6.png

例2:有三个进程分别是进程C(写端)和进程A、B (读端),写端写入数据“Hello World”,A读端读取数据,B再去读取。
写端C:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <assert.h>#include <unistd.h>#include <fcntl.h>int main(){    int fw = open("myfifo",O_WRONLY);    assert(fw != -1);    write(fw,"helloworld",10);//  sleep(2);//  write(fw,"how are you?",12);//继续写入数据    sleep(15);//  close(fw);    return 0;}

读端A

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <assert.h>#include <unistd.h>#include <fcntl.h>int main(){    int fr = open("myfifo",O_RDONLY);    assert(fr != -1);    char buf[128] = {0};    int len = read(fr,buf,5);    if(len != -1)    {        printf("buf:%s\n",buf);    }    sleep(10);//  close(fr);    return 0;}

读端B

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <assert.h>#include <unistd.h>#include <fcntl.h>int main(){    int fr = open("myfifo",O_RDONLY);    assert(fr != -1);    char buf[128] = {0};    int len = read(fr,buf,5);    if(len != -1)    {        printf("buf:%s\n",buf);    }    sleep(10);//  close(fr);    return 0;}

输出结果如下:

进程间通信之管道7.png

其中test1.c和test3.c是读文件,test2.c是写文件,需要关注的是写端和读端的关闭时间,只有写端开着,读端才能读到数据,而且如果有一个读端关的早,写端如果再写入数据会产生异常,系统会按默认处理异常信号,然后关闭,这样的话另一个读端也不能读取数据。

总结:
(1)两个进程运行时,写端彻底关闭,则读端read返回0,也会关闭。
(2)如果管道的读端关闭,继续写入数据会发生异常,写端收到未捕获的信号SIGPIPE会关闭写端。当然也可以修改默认的信号响应方式,比如增加信号处理函数。
(3)写端写入数据以后,读端不从里面读取内容:数据保持在管道中存在一段时间。管道文件的大小是0。
(4)管道通讯发送的数据若没有指定进程接收,任何一个进程只要打开的是同一个管道文件,都有可能读到数据。
(5)read读取管道中的数据,只要读过的数据就会被清空 。

有名管道和无名管道的异同点

1、相同点
open打开管道文件以后,在内存中开辟了一块空间,管道的内容在内存中存放,有两个指针—-头指针(指向写的位置)和尾指针(指向读的位置)指向它。读写数据都是在给内存的操作,并且都是半双工通讯。
2、区别
有名在任意进程之间使用,无名在父子进程之间使用。

拓展:
全双工、半双工、单工通讯的区别:
单工:方向是固定的,只有一个方向可以写,例如广播。
半双工:方向不固定,但在某一刻只能有一个方向进行写,例如对讲机。
全双工:两个方向都可以同时写,例如打电话。

阅读全文
0 0