进程间通信(一)---管道

时间:2022-12-03 19:08:08

进程间通信的目的:

1、数据传输:一个进程需要将他的数据发送给另一个进程
2、资源共享:多个进程间共享同样的资源
3、通知事件:一个进程需要向另一个或一组进程发送消息,通知他(他们)发生了某种事件(如进程终止时要通知父进程)。
4、进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道他的状态。

进程间通信分为以下几种
1、管道:匿名管道和命名管道
2、ystem V :消息队列,共享内存,信号量
3、POSIX IPC:消息队列,共享内存,信号量,互斥量,条件变量,读写锁

下面我们先来看一看管道

首先我们需要知道管道是什么:管道是一种进程间通信的方式。我们把从一个进程连接到另一个进程的一个数据流称为一个管道。

管道又分为命名管道匿名管道

匿名管道:只允许具有亲缘关系的进程间通信

#include<unistd.h>
//创建一个无名管道
int pipe(int fd[2]);
//参数:fd为文件描述符组,其中fd[0]表示读端,fd[1]表示写端
//返回值:成功返回0,失败返回错误码

进程间通信(一)---管道
下面以代码实例说明:从键盘(标准输入)读取数据,写入管道,读取管道,写到屏幕(标准输出)

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

int main()
{
    //定义一个文件描述符组
    int fds[2];
    char buf[100];
    int len;
    //创建一个无名管道(即匿名管道)
    if(pipe(fds) == -1)
    {
        perror("make pipe"),
            exit(1);
    }
    //从标准输入(键盘)读数据放在buf数组里
    while(fgets(buf,100,stdin))
    {
        len = strlen(buf);

        //将每次从标准输入(键盘)读取的数据写到管道里
        //文件描述符组:fds[1]表示写端
        if(write(fds[1],buf,len) != len)
        {
            perror("write to pipe");
            break;
        }
        //将buf清零,以便下一次往其中读入数据
        memset(buf,0x00,sizeof(buf));
        //从管道中读取刚刚写入的数据
        //文件描述符组:fds[0]表示读端
        if((len = read(fds[0],buf,100)) == -1)
        {
            perror("read from pipe");
            break;
        }
        //将其写入到标准输出(显示器)中
        //1表示标准输出,上面的fds[1]表示写端
        if(write(1,buf,len) != len)
        {
            perror("write to stdout");
            break;
        }
    }
}

运行结果如下:每次当我们输入以后按下回车就会再显示一遍我们所输入的内容
进程间通信(一)---管道

管道的特点:

1:只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常情况下,一个管道有一个进程创建,然后该进程调用fork,此后父进程与子进程就可应用该管道。
2:管道提供流式服务(面向字节流)。
3:一般而言,进程退出,管道释放,所以管道的生命周期随进程。
4:内核会对管道操作进行同步与互斥。
5:管道是半双工的,数据只能向一个方向流动,需要双方通信时,要建立起两个管道。

用fork共享管道原理
进程间通信(一)---管道

站在文件描述符角度深度理解管道
进程间通信(一)---管道
进程间通信(一)---管道
进程间通信(一)---管道
我们看待管道,就像看待文件一样。管道的使用和文件一致,这刚好也符合Linux下一切皆文件的思想。
管道的读写规则:(如上图,父进程进行读,子进程进行写)

当没有数据可读的时候:
情况1:父进程调用读(read)操作就会阻塞,进程暂停执行。一直等到有数据到来为止。
情况2:父进程调用读(read)操作返回-1,errno值为EAGAIN。
当管道满的时候:
情况1:子进程调用写(write)操作阻塞,直到有进程读走数据。
情况2:子进程调用写(write)操作返回-1,errno值为EAGAIN。
假如说1:子进程一直写,但一段时间之后不写了并且还关闭了写端,则父进程会一直读,直到数据读完返回0.
假如说2:子进程写,父进程关闭读端,则子进程会异常终止,父进程会得到子进程的退出信号(13:SIGPIPE)。

命名管道
匿名管道应用的一个限制就是只能在具有共同祖先的进程间通信。如果我们想在不想管的进程之间交换数据,可以使用FIFO文件来做这项工作,他经常被叫做为命名管道。命名管道是一种特殊类型的文件。
命名管道与匿名管道的区别就在于,匿名管道的有pipe函数创建并打开,命名管道有mkfifo函数创建,打开需要用open。FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在与他们的创建与打开方式不同,一旦这些工作完成以后,他们具有相同的语义。下面我们就来看一看命名管道的创建与打开。
创建命名管道
1:命名管道可以在命令行上创建,使用下面的命令
mkfifo filename
2:命名管道也可以在函数中创建,相关的函数:
int mkfifo(const char *filename,mode_t mode);
创建一个:

int main(int argc,char *argv[])
{
    mkfifo("p",0644);
    return 0;
}

打开命名管道的规则
1:如果当前打开命名管道是为了读
阻塞知道有相应进程为写而打开该管道
立刻返回成功
2:如果当前打开命名管道是为了读
阻塞知道有相应进程为读而打开该管道
立刻返回失败,错误码为ENXIO
代码实例:用命名管道实现两进程间的通信

**Makefile文件**
.PHONY:all

all:process1.c process2.c

process1:process1.c
    gcc $^ -o $@

process2:process2.c
    gcc $^ -o $@

.PHONY:clean

clean:
    rm -f process1 process2

**process1.c文件**
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stst.h>

int main()
{
    umask(0);
    if(mkfifo("mypipe",0644) < 0)
    {
        perror("mkfifo");
        exit(EXIT_FAILURE);
    }
    int rfd = open("mypipe",O_RDONLY);
    if(rfd < 0)
    {
        perror("open");
        exit(EXIT_FAILURE);

    }
    char buf[1024];
    while(1)
    {
        buf[0]=0;
        printf("Please wait...\n");
        ssize_t s = read(rfd,buf,sizeof(buf)-1);
        if(s > 0)
        {
            buf[s-1] = 0;
            printf("process2 say# %s\n",buf);
        }
        else if(s == 0)
        {
            printf("process2 quit,exit now.\n");
            exit(EXIT_SUCCESS);
        }
        else
        {
            perror("read");
            exit(EXIT_FAILURE);
        }

    }
    close(rfd);
    return 0;
}


**process2.c文件**
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<string.h>

int main()
{
    int wfd = open("mypipe",O_WRONLY);
    if(wfd < 0)
    {
        perror("open");
        exit(EXIT_FAILURE);             
    }
    char buf[1024];
    while(1)
    {
        buf[0]=0;
        printf("Please enter#\n");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf)-1);
        if(s > 0)
        {
            buf[s] = 0;
            write(wfd,buf,strlen(buf));
        }
        else if(s <= 0)
        {
            perror("read");
            exit(EXIT_FAILURE);             
        }                                           
    }
    close(wfd);
    return 0;
}

运行结果如下:进程间通信(一)---管道