进程间通信之管道(用水管思维理解)

Nothing is ever a setback. If anghing, it just motivate you for what is next.--------没有挫折这回事,他若具有任何意义,那只是激励你再下一个挑战中表现更好.

1,前景回顾

进程间的通信肯定是与进程相关,小黑在之前写过相关的博客,有兴趣的或者对这方面这知识有点生疏的同学可以移步:

https://blog.csdn.net/weixin_46027505/article/details/104812719

https://blog.csdn.net/weixin_46027505/article/details/105141592

2,管道真的就是管道

  • 在介绍管道前有必要了解一下3种通信方式。

1, 单工通信:传输方向只有一个方向,单工通信只有一根数据线,它也只在一个方向上进行,如打印机、电视机等。比如:电视机,广播
2, 半双工通信:可以双向通信,但只能轮流传输,也只有一根数据线,不同于单工通信的是这根数据线即可作为发送又可作为接收,虽数据可在两个方向上传送,但通信双方不能同时收发数据。比如:对讲机
3, 全双工通信:可以同时双向传输数据,数据的发送和接收用两根不同的数据线,通信双方在同一时刻都能进行发送和接收,发送和接收同时进行,没有延迟。比如视频聊天

2.1 管道通俗理解

  • 为什么这种通信机制叫做管道呢? 因为它的功能真的就是生活中管道(水道)的作用。

我们把供水商比作一个进程空间,我们自己家是另外一个进程空间,管道就是用来送水的,水就是我们进程间通信传输的数据,往水管里注水就相当于向文件写入数据
这里需要记住,管道是单向的、先进先出的,这个容易理解,就像供水商给你送水时,你不能向这个水管注水。

2.2 为什么管道是半双工通信?

由于Linux一个命令只能完成一个功能,所以一个复杂点的任务需要好几个进程协同完成,第一个进程处理结果需交给第二个进程,然后一次交给第三个,等等,像流水线完成某个商品的生产一样,这个过程只需要数据单向往下传输,所以设计的时候做成了半双工。
当然也可以使用管道实现双工通信,需要两个管道。

2.3 管道特点

管道是,它把一个进程的输出和另一个进程的输入连接在一起。

1, 一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
管道是固定读端和写端的。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

2, 数据被一个进程读出后,将被从管道中删除,其它读进程将不能再读到这些数据。

3, 管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞

4,管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。

5, 向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。

6, 只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号,应用程序可以处理该信号,也可以忽略(默认动作则是应用程序终止)。

  • 当然管道有两种类型。
    1,无名管道:这个只能用于父子进程,这个你可以理解成,你和你爸爸分家了,这个管道就是用来你们两家送水用的。因为这个管道你和爸爸
    2,命名管道:可以用于同一系统中,任何进程间通信。

后面马上会介绍到。

3,无名管道(有时会直接叫做管道,不要混淆)

管道是UNIX系统IPC的最古老的形式,所有的UNIX系统都提供此种通信机制。管道的实质是一个内核缓冲区,进程以先进 先出(FIFO, First In First Out)的方式从缓冲区存取数据:管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺 序地读取数据,该缓冲区可以看做一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后再缓冲区都 不复存在了。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或写进程是否进入等待队列,当空的缓冲区有新数据写 入或慢的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写。

3.1 局限性

(1)半双工,数据只能在一个方向流动,现在有些系统可以支持全双工管道,但是为了最佳的可移植性,应认为系统 不支持全双工管道;

(2)无名管道只能用于具有父子进程关系通信;

#3 3.2 无名管道的创建
管道可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不 属于其他任何文件系统,并且只存在于内存中。
管道是通过调用pipe函数创建的。

#include <unistd.h>
int pipe(int fd[2]); 
  • 返回值:若成功,返回0,若出错,返回-1.
    经由参数fd返回的两个文件描述符:
    fd[0]只为读而打开(用来在管道里取水)
    fd[1]只为写而打开 (用来在管道里注水)
    fd[1]的输出是fd[0]的输入

4,用无名管道实现单进程通信

其实管道就是文件,下面的示例代码就是自己给自己发送信息然后读出来。
在这里插入图片描述

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>


#define MSG "my name is xuxiaohei"

int main(int argc, char **argv)
{
    int     pipe_fd[1];
    int     rv;
    char    buf[512];


   
    if (pipe(pipe_fd) < 0)
    {
        printf("Create pipe failure: %s\n", strerror(errno));
        return -1;
    }

    memset(buf, 0, sizeof(buf));
        
    if (write(pipe_fd[1], MSG, strlen(MSG)) < 0)
    {
        printf(" write data to pipe failure: %s\n", strerror(errno));
        return -2;
    }
    
    rv = read(pipe_fd[0], buf, sizeof(buf));
    if (rv < 0)
    {
            printf(" read from pipe failure: %s\n", strerror(errno));
            return -3;
    }
    printf("process read %d bytes data from pipe: \"%s\"\n", rv, buf);

      

    return 0;
}

在这里插入图片描述

5,用无名管道实现父子进程通信

下面编写一个例程,用于父进程给子进程方向发送数据。首先父进程创建管道之后fork(),这时子进程会继承父进程所有打开的 文件描述符(包括管道),这时对于一个管道就有4个读写端(父子进程各有一对管道读写端),如果需要父进程往子进程里写 数据,则需要在父进程中关闭读端,在子进程中关闭写端;而如果需要子进程往父进程中写数据,则可以在父进程关闭写端,然 后子进程中关闭读端。
在这里插入图片描述

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

#define MSG_STR "This message is from father: Hello, my son!"

int main(int argc, char **argv)
{
    int     pipe_fd[1];
    int     rv;
    int     pid;
    char    buf[512];
    int     wstatus;

    printf("before creat pipe\n");
    if (pipe(pipe_fd) < 0)
    {
        printf("Create pipe failure: %s\n", strerror(errno));
        return -1;
    }


    printf("before fork\n");
    if ((pid = fork()) < 0)
    {
        printf("Create child process failure: %s\n", strerror(errno));
        return -2;
    }
    else if (pid == 0)
    {
        printf("son start read message from father\n");
        close(pipe_fd[1]);
        memset(buf, 0, sizeof(buf));
        rv = read(pipe_fd[0], buf, sizeof(buf));
        if (rv < 0)
        {
            printf("Child process read from pipe failure: %s\n", strerror(errno));
            return -3;
        }
        printf("Child process read %d bytes data from pipe: \"%s\"\n", rv, buf);
        return 0;
    }




    close(pipe_fd[0]);
    if (write(pipe_fd[1], MSG_STR, strlen(MSG_STR)) < 0)
    {
        printf("Parent process write data to pipe failure: %s\n", strerror(errno));
        return -3;
    }


    printf("father finish write and wait son read \n");
    wait(&wstatus);


    return 0;
}

关闭管道的一端     
(1)当读一个写端被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束;     
(2)当写一个读端被关闭的管道时,则产生信号SIGPIPE,如果忽略该信号或者捕捉该信号并从其处理程序返回,则 wirte返回-1.

在这里插入图片描述

如果想要子进程给父进程发信息

在这里插入图片描述

6,有名管道FIFO(或者叫做命名管道)

当然,有名管道同样是半双工的。

前面讲到的未命名的管道只能在两个具有亲缘关系的进程之间通信,
通过命名管道(Named PiPe)FIFO,不相关的进程也能 交换数据。

它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),它在磁盘上有对应的节点,但 没有数据块——换言之,只是拥有一个名字和相应的访问权限,通过mknode()系统调用或者mkfifo()函数来建立的。一旦建 立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。当不再被 进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。

因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。

7,无名管道和有名管道的区别

两者之间最大的区别就是

有名在任意两个进程间可以通信
无名只能在父子进程间通信

关于命名管道小黑另外写一篇博客实现一个小应用。
https://blog.csdn.net/weixin_46027505/article/details/105157072

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值