进程间通信(1)--管道pipe

xiaoxiao2021-02-28  43

学习Linux的知识中,有一个特别重要的概念叫“进程”,而要进行进程间通信时,有一个特别重要的概念就是--管道,今天,我们就来学习一下什么是管道,它能又干什么呢?

一、概念

管道:把一个进程连接到另外一个进程的一个数据流称为管道。

          (其实,我们联系现实生活,自来水管可以将我们用户和供水站连接起来,通过管道运输水流,在这里可以借助这个例子              帮助我们理解进程间通信中管道的概念)

二、匿名管道

1.原型:

int pipe(int fd[2]); //fd为文件描述符,其中fd[0]表示读端,fd[1]表示写端

2.下面,我们来实现一段从键盘读取数据,写入管道,读取管道,写到屏幕的代码

#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> int main(void) { int fds[2]; char buf[100]; int len; if (pipe(fds) == -1) perror("make pipe"), exit(1); //读取数据 while (fgets(buf, 100, stdin)){ len = strlen(buf); //写入管道 if (write(fds[1], buf, len) != len){ perror("write to pipe"); break; } memset(buf, 0x00, sizeof(buf)); //读取管道 if ((len = read(fds[0], buf, 100)) == -1){ perror("read from pipe"); break; } //写到屏幕 if (write(1, buf, len) != len){ perror("write to stdout"); break; } } }

3.通过父进程fork出子进程通过管道连接的过程如下:

(1)父进程创建管道

(2)父进程fork出子进程(左边父进程,右边子进程)

(3)父进程关闭fd[0],子进程关闭fd[1]

管道的读写操作是相对应的,当父进程fork出子进程时,两边对同时打开读写端,这样会导致程序不知道到底在管道哪一端读数据,哪一端写数据。因此,我们必须关闭关闭父进程的一端和子进程对应的另外一端,这样才能进行正常的读写操作。

说明:管道具有原子性,当写入管道的数据不大于pipe_buf时,linux将保证写入数据的原子性;当写入管道的数据大于pipe_buf时,linux将不保正写入数据的原子性。

4.匿名管道的特点

(1)只能用于具有共同祖先的进程(有亲缘关系)之间进行通信。通常,一个管道由一个进程创建,然年该进程调用fork,之后          父子进程就可以用该管道进行通信;

(2)管道提供流式服务;

(3)管道的生命周期随进程(一般而言,进程退出,管道释放);

(4)一般而言,内核会对管道进行同步和互斥;

(5)管道是半双工的,数据只能由一个方向流动;双方通信时,需要建立两个管道。

这里可能会有一些比较陌生的词语,我们来解释一下:

(1)把两个进程看到的公共资源叫做临界资源,这两个进程的代码叫做临界区;

(2)管道在访问临界资源时,既要保证互斥,又要保证访问的原子性;

(3)同步是指在保证数据安全时,按照某种特性进行。

三、命名管道

看了匿名管道之后,我们肯定会有疑问,具有亲缘关系的管道之间可以进行通信,那么几个不相关的进程之间是否可以进行通呢?这里我们就引进了命名管道的概念。

1.命名管道:通过FIFO文件来进行不相关进程的通信,命名管道是一种特殊类型的文件

2.命名管道的创建

(1)在命令行创建

$ mkfifo filename

(2)从程序里创建

int mkfifo(const char * filename, mode_t mode);

(3)创建

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

四、匿名管道和命名管道的区别

1.匿名管道由pipe函数创建并打开;而命名管道由mkfifo,用open打开。

2.匿名管道(pipe)和命名管道(FIFO)最大的差别就是管道的创建和打开不同,一旦工作完成后,它们具有相同的语义。

下面,我们用命名管道来实现文件拷贝

读取文件,写入管道:

#include<stdio.h> #include<unistd.h> #include<string.h> #include<errno.h> #include<stdlib.h> #define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while (0) int main(int argc, char * argv[]) { mkfifo("tp", 0644); int infd; infd = open("abc", O_RDONLY); if (infd == -1) ERR_EXIT("open"); int outfd; outfd = open("tp", O_WRONLY); if (outfd == -1) ERR_EXIT("open"); char buf[1024]; int n; while ((n == read(infd, buf, 1024)) > 0) { write(outfd, buf, n); } close(infd); close(outfd); return 0; }

读取管道,写入目标文件:

#include<stdio.h> #include<unistd.h> #include<string.h> #include<errno.h> #include<stdlib.h> #define ERR_EXIT(m)\ do\ {\ perror(m); \ exit(EXIT_FAILURE); \ }while (0) int main(int argc, char * argv[]) { int outfd; outfd = open("abc.bak",O_WRONLY|O_TRUNC,0644); if (outfd == -1) ERR_EXIT("open"); int infd; outfd = open("tp", O_RDONLY); if (outfd == -1) ERR_EXIT("open"); char buf[1024]; int n; while ((n = read(infd, buf, 1024)) > 0) { write(outfd, buf, n); } close(infd); close(outfd); unlink("tp"); return 0; }
转载请注明原文地址: https://www.6miu.com/read-2612383.html

最新回复(0)