进程间的六种通信方式——管道 命名管道 详解

本文详细介绍了Unix系统中的管道(Pipe)通信方式,包括匿名管道和命名管道(FIFO)。匿名管道适用于具有亲缘关系的进程间通信,通过fork创建进程并利用管道实现数据传递。命名管道则允许不相关进程间的通信,通过mkfifo创建并在磁盘上有对应文件。文中还提供了多个代码示例,展示了如何在C语言中使用管道进行进程间通信。
摘要由CSDN通过智能技术生成

本篇紧接上篇信号——管道

进程间的六种通信方式:

  • 信号(Signal )

  • 管道(Pipe)

  • socket

  • 信号量(Semaphore)

  • 共享内存(Shared Memory)

  • 消息队列(Message Queue)

管道

管道是UNIX系统lPC的最古老的形式,所有的UNIX系统都提供此种通信机制。

如果你学过 Linux 命令,那你肯定很熟悉「|」这个竖线。

$ ps auxf | grep mysql

上面命令行里的「|」竖线就是一个管道,它的功能是将前一个命令(ps auxf)的输出,作为后一个命令(grep mysql)的输入,从这功能描述,可以看出管道传输数据是单向的,如果想相互通信,我们需要创建两个管道才行。

同时,我们得知上面这种管道是没有名字,所以「|」表示的管道称为匿名管道,用完了就销毁。

1、两个局限性:
(1)半双工,数据只能在一个方向流动,现在有些系统可以支持全双工管道,但是为了最佳的可移植性,应认为系统不支持全双工管道;
(2)管道只能在具有公共祖先之间的两个进程之间使用;

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

#include <unistd.h>

int pipe(int fd[2]);

返回值:若成功,返回0;若出错,返回-1.

经由参数fd返回的两个文件描述符:fd[0]为读而打开,fd[1]为写而打开,fd[1]的输出是fd[0]的输入。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5tH24t09-1659014537117)(C:\Users\86138\AppData\Roaming\Typora\typora-user-images\image-20220728200822350.png)]

看到这,你可能会有疑问了,这两个描述符都是在一个进程里面,并没有起到进程间通信的作用,怎么样才能使得管道是跨过两个进程的呢?

通常,进程会先调用pipe,接着调用fork,从而创建了父进程与子进程的IPC通道。

这样就做到了两个进程都有「 fd[0]fd[1]」,两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eDCWxIo1-1659014537120)(C:\Users\86138\AppData\Roaming\Typora\typora-user-images\image-20220728201319421.png)]

fork之后做什么取决于我们想要的数据流的方向,对于从子进程到父进程,父进程关闭管道的写端fd[1],子进程关闭读端fd[0]。

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

代码示例:

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

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

#define MSG_STR "Hello, my child! How are you?"

int main(int argc, char *argv[])
{
        int             pipe_fd[2];
        int             rv = -1;
        int             pid;
        int             w_status;
        char            buf[64];

        pipe(pipe_fd); 		//创建管道

        if((pid = fork()) < 0)		//创建进程
        {
                printf("fork failure: %s\n", strerror(errno));
                return -1;
        }

        else if(0 == pid)       //子进程,读消息
        {
                close(pipe_fd[1]);	//关闭写端

                memset(buf, 0, sizeof(buf));
                rv = read(pipe_fd[0], buf, sizeof(buf)); 	//开始读,没有消息就等待
                if(rv < 0)
                {
                        printf("read failure: %s\n", strerror(errno));
                        return -1;
                }
                printf("Child read data from parent: %s\n", buf);
        }

        else    //父进程,写内容
        {
                close(pipe_fd[0]); 	//关闭读端

                write(pipe_fd[1], MSG_STR, strlen(MSG_STR)); //写数据

                wait(&w_status);
        }

        return 0;
}

运行结果:

Child read data from parent: Hello, my child! How are you?

到这里,我们仅仅解析了使用管道进行父进程与子进程之间的通信,但是在我们 shell 里面并不是这样的。

在 shell 里面执行 A | B命令的时候,A 进程和 B 进程都是 shell 创建出来的子进程,A 和 B 之间不存在父子关系,它俩的父进程都是 shell。(只能说他们之间存在亲缘关系)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nBcvGun6-1659014537121)(C:\Users\86138\AppData\Roaming\Typora\typora-user-images\image-20220728203335929.png)]

所以说,在 shell 里通过「|」匿名管道将多个命令连接在一起,实际上也就是创建了多个子进程,那么在我们编写 shell 脚本时,能使用一个管道搞定的事情,就不要多用一个管道,这样可以减少创建子进程的系统开销。

我们可以得知,对于匿名管道,它的通信范围是存在父子关系的进程。因为管道没有实体,也就是没有管道文件,只能通过 fork 来复制父进程 fd 文件描述符,来达到通信的目的。

所以管道还有另外一个类型是命名管道(Named PiPe),也被叫做 FIFO,因为数据是先进先出(FIFO,First ln First Out)的传输方式。

在使用命名管道前,先需要通过 mkfifo 命令来创建,并且指定管道名字:

$ mkfifo myfifo

myfifo 就是这个管道的名称,基于 Linux 一切皆文件的理念,所以管道也是以文件的方式存在,我们可以用 ls 看一下,这个文件的类型是 p,也就是 pipe(管道) 的意思:

$ ls -l myfifo
prw-r--r-- 1 zhenyu zhenyu 0 Jul 28 20:40 myfifo

前面讲到的匿名管道只能在两个具有亲缘关系的进程之间通信,通过命名管道(Named PiPe)FIFO,不相关的进程也能交换数据。它在磁盘上有对应的节点,但没有数据块——换言之,只是拥有一个名字和相应的访问权限,通过mknode()系统调用或者mkfifo()函数来建立的。一旦建立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。

代码示例:

下面这个例程创建了两个掩藏的命名管道文件(.fifo_chat1和.fifo_chat2)在不同的进程间进行双向通信。

该程序需要运行两次(即两个进程),其中进程0(mode=0)从标准输入里读入数据后通过命名管道2(.fifo_chat2)写入数据给进程1 (mode=1) ;而进程1 (mode=1)则从标准输入里读入数据后通过命名管道1 (.fifo_chat1)写给进程0,这样使用命名管道实现了一个进程间聊天的程序。

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <libgen.h>
#include <wait.h>

#define FIFO_FILE1 ".fifo_chat1"
#define FIFO_FILE2 ".fifo_chat2"

int             stop = 0;

void sig_pipe(int signum)		//上一篇中讲到的信号,这是指定的信号的动作
{
        if(SIGPIPE == signum)
                printf("SIGPIPE really get...\n");
                stop = 1;
}

int main(int argc, char *argv[])
{
        int             rv = -1;
        int             mode;
        int             fifo_rfd, fifo_wfd;
        fd_set          rdset;
        char            buf[64];

        if(NULL == argv[1]) 	//通过参数来选择模式,模式不同的才能互相通信
        {
                printf("usage:./%s [0/1]\n", basename(argv[0]));
                printf("this chat program need run twice, must select arguments among 0 or 1 in difference!!!\n");
                return -1;
        }

        if(access(FIFO_FILE1, F_OK) < 0)	//判断文件是否存在
        {
                mkfifo(FIFO_FILE1, 0666);
                printf("%s create success...\n", FIFO_FILE1);
        }

        if(access(FIFO_FILE2, F_OK) < 0)
        {
                mkfifo(FIFO_FILE2, 0666);
                printf("%s create success...\n", FIFO_FILE2);
        }

        signal(SIGPIPE, sig_pipe);		//安装信号,(13)SIGPIPE

        mode = atoi(argv[1]);
        if(0 == mode)
        {
                printf("mode[0] open '%s' to read...\n", FIFO_FILE1);
                
                /*  打开chat1的读端  */
                fifo_rfd = open(FIFO_FILE1, O_RDONLY);
                

                printf("mode[0] open '%s' to write...\n", FIFO_FILE2);
                
                /*  打开chat2的写端  */
                fifo_wfd = open(FIFO_FILE2, O_WRONLY);
                
        }
        else    //mode == 1
        {
                printf("mode[1] open '%s' to write...\n", FIFO_FILE1);
                
                /*  打开chat1的写端  */
                fifo_wfd = open(FIFO_FILE1, O_WRONLY);
               
                printf("mode[1] open '%s' to read...\n", FIFO_FILE2);
                
                /*  打开chat2的读端  */
                fifo_rfd = open(FIFO_FILE2, O_RDONLY);
              
        }

        while(!stop)
        {
                FD_ZERO(&rdset);
                FD_SET(STDIN_FILENO, &rdset);
                FD_SET(fifo_rfd, &rdset);

                rv = select(fifo_rfd+1, &rdset, NULL, NULL, NULL);
                if(rv <= 0)
                {
                        printf("select() failure or get timeout: %s\n", strerror(errno));
                        break;
                }
                //rv > 0
                if(FD_ISSET(STDIN_FILENO, &rdset) )
                {
                        memset(buf, 0, sizeof(buf));
                        
                        /*  从标准输入读取内容,并发送(写)数据  */
                        fgets(buf, sizeof(buf), stdin);  
                        write(fifo_wfd, buf, sizeof(buf));
                }
                else    //fifo_rfd
                {
                        rv = read(fifo_rfd, buf, sizeof(buf));
                        if(rv < 0)
                        {
                                printf("read data in chat failure: %s\n", strerror(errno));
                                break;
                        }

                        else if(0 == rv)
                        {
                                printf("Another side of fifo get closed...\n");
                                break;
                        }

                        printf("<-- %s", buf);
                }
        }
        return 0;
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mDSQP9uv-1659014537122)(C:\Users\86138\AppData\Roaming\Typora\typora-user-images\image-20220728211622663.png)]

这里我们可以看到,这是我运行的两个不同的进程,实现的不同进程间的通信(没有亲缘关系),他们之间靠着管道文件作为纽带实现不同进程间的数据交换。

参考链接:https://blog.csdn.net/modi000/article/details/122084165

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值