0%

Linux管道通信

什么是管道

​ 管道, 英文名 pipe 。

​ 在系统操作和执行命令的时候,通常需要将一个程序的输出交给另外一个程序处理,这样的操作虽然可以使用输入输出重定向加文件实现,但是比较麻烦。

​ 管道的概念就是这样诞生,将两个进程的输入和输出通过一个管道相连,以便达到进程之间相互通信的目的。

​ 从本质上来说,管道就是一个文件,前面的的进程以写的方式打开文件,后面的程序以读的方式打开文件,利用这样的方式实现进程间通信。

​ 虽然实现形态上是文件,但是管道本身并不占用磁盘或者其他外部存储的空间。在Linux的实现上,它占用的是内存空间。所以,Linux上的管道就是一个操作方式为文件的内存缓冲区。


管道的分类

  • 匿名管道

  • 命名管道

    匿名管道只能在父进程和子进程之间使用,在父进程产生子进程之前,打开一个管道文件,然后再fork()

产生一个子进程,由于父子进程共享文件描述符,所以可以通过这个管道文件相互通信。

匿名管道的调用原型:

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

大致执行示意图如下实现了半双工通信

半双工通信

一个最简单的管道系统编程代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

#define STRING "hello world!"

int main()
{
int pipefd[2]; //为pipe准备文件标识符
pid_t pid;
char buf[BUFSIZ];

if (pipe(pipefd) == -1) { //使用pipe()系统调用
perror("pipe()");
exit(1);
}

pid = fork(); //创建子进程
if (pid == -1) {
perror("fork()");
exit(1);
}

if (pid == 0) {
/* this is child. */
printf("Child pid is: %d\n", getpid());
if (read(pipefd[0], buf, BUFSIZ) < 0) { //子进程读管道中父进程写入的信息
perror("write()"); //没有读到的话就是写出现了问题
exit(1);
}

printf("%s\n", buf); //将从父进程得到的信息打印

bzero(buf, BUFSIZ); //将buf清零
//将要送给parent的信息写入buf中
snprintf(buf, BUFSIZ, "Message from child: My pid is: %d", getpid());
//调用pipe()的写入实现
if (write(pipefd[1], buf, strlen(buf)) < 0) {
perror("write()");
exit(1);
}

} else {
/* this is parent */
printf("Parent pid is: %d\n", getpid());
//将要送给子进程的信息写入buf
snprintf(buf, BUFSIZ, "Message from parent: My pid is: %d", getpid());
//调用pipe()的写入实现
if (write(pipefd[1], buf, strlen(buf)) < 0) {
perror("write()");
exit(1);
}

sleep(1); //父进程写入完成以后,等待子进程完成读取

bzero(buf, BUFSIZ);
//读取管道中子进程写入的信息
if (read(pipefd[0], buf, BUFSIZ) < 0) {
perror("write()");
exit(1);
}

printf("%s\n", buf);

wait(NULL);
}


exit(0);
}

​ 程序运行效果如下,父进程先给子进程发一个消息,子进程读到消息后将消息打印,然后再给父进程发消息,父进程读到消息后打印从子进程收到的消息。

一般来说不使用父子进程之间的半双工通信,管道的推荐使用方法是单工模式,即一个进程只写入管道,一个进程只读入管道 ,不需要使用的标识符就将其关闭。

单工

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

#define STRING "hello world!"

int main()
{
int pipefd[2];
pid_t pid;
char buf[BUFSIZ];

if (pipe(pipefd) == -1) {
perror("pipe()");
exit(1);
}

pid = fork();
if (pid == -1) {
perror("fork()");
exit(1);
}

if (pid == 0) {
/* this is child. */
close(pipefd[1]); //子进程只读,所以关闭写入

printf("Child pid is: %d\n", getpid());
if (read(pipefd[0], buf, BUFSIZ) < 0) {
perror("write()");
exit(1);
}

printf("%s\n", buf);

} else {
/* this is parent */
close(pipefd[0]);//父进程只写入,所以关闭读

printf("Parent pid is: %d\n", getpid());

snprintf(buf, BUFSIZ, "Message from parent: My pid is: %d", getpid());
if (write(pipefd[1], buf, strlen(buf)) < 0) {
perror("write()");
exit(1);
}

wait(NULL);
}
exit(0);

​ 命名管道不仅可以在父子进程中实现消息的传递,通过命名可以实现任意进程之间的管道通信,此篇博客不再详细介绍命名管道。