管道分为有名管道和无名管道
无名管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系一般指的是父子关系。无明管道一般用于两个不同进程之间的通信。当一个进程创建了一个管道,并调用fork创建自己的一个子进程后,父进程关闭读管道端,子进程关闭写管道端,这样提供了两个进程之间数据流动的一种方式。
有名管道也是一种半双工的通信方式,但是它允许无亲缘关系进程间的通信。
无名管道
创建
1
2
3
| #include <unistd.h>
int pipe(int fd[2]);
|
参数:
fd参数返回两个 文件描述符
- fd[0]: 指向管道的读端
- fd[1]: 指向管道的写端
fd[1]的输出是fd[0]的输入
返回值
管道如何实现进程间的通信
- 父进程创建管道,得到两个件描述符指向管道的两端
- 父进程fork出子进程,子进程也有两个文件描述符指向同管道。
- 父进程关闭fd[0],子进程关闭fd[1],即子进程关闭管道读端,父进程关闭管道写端(因为管道只支持单向通信)。子进程可以往管道中写,父进程可以从管道中读,管道是由环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
代码实现
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
| #include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret == -1)
{
perror("pipe error");
return -1;
}
printf("main process:%d\n", getpid());
pid_t id = fork();
if(id == 0)
{
int i = 0;
close(fd[0]);
printf("child process:%d\n", getpid());
char *child = "i am child!";
while(i<5)
{
write(fd[1], child, strlen(child)+1);
sleep(2);
i++;
}
}
else if(id > 0)
{
close(fd[1]);
char msg[100];
int j = 0;
printf("father process:%d\n", getpid());
while(j < 5)
{
memset(msg, '\0', sizeof(msg));
ssize_t s = read(fd[0], msg, sizeof(msg));
if(s > 0)
{
msg[s-1] = '\0';
}
printf("%s\n", msg);
j++;
}
}
else
{
perror("fork error\n");
return -1;
}
return 0;
}
|
执行结果: 每隔2秒打印一次i am child,共打印5次
1
2
3
4
5
6
7
8
9
| $ ./a.out
main process:844
father process:844
child process:845
i am child!
i am child!
i am child!
i am child!
i am child!
|
管道大的一些问题
- 读端不读,写端一直写。写端会一直写下去,写满后会导致阻塞,直到有空闲空间才能再次写入。
- 读端一直读,写端不写。管道中的剩余数据读完后,会等到管道中有数据读取时才会继续执行read
- 子进程一直在写,父进程读过一会关闭,导致写端的进程收到一个SIGPIPE信号,导致进程终止,只写不读会导致read返回0,就像读到文件末尾一样。
管道的特点
1.管道只允许具有血缘关系的进程间通信,如父子进程间的通信。
2.管道只允许单向通信。
3.管道内部保证同步机制,从而保证访问数据的一致性。
4.面向字节流
5.管道随进程,进程在管道在,进程消失管道对应的端口也关闭,两个进程都消失管道也消失。
有名管道
命名管道在底层的实现跟匿名管道完全一致,区别只是命名管道会有一个全局可见的文件名以供别人open打开使用。再程序中创建一个命名管道文件的方法有两种,一种是使用mkfifo函数,另一种是使用mknod系统调用。
mkfifo
1
2
3
4
| #include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename,mode_t mode);
|
参数:
- filename: 管道文件名
- mode: 指定权限
返回值:
例子
读端代码
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
| #include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <error.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = 0;
fd = mkfifo("./fifo", 0600);
if(fd < 0) {
perror("mkfifo error");
//return -1;
}
fd = open("./fifo", O_RDONLY);
char msg[200] = {0};
while(1)
{
memset(msg, 0, sizeof(msg));
read(fd, msg, sizeof(msg));
printf("msg:%s\n", msg);
sleep(2);
}
return 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
| #include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <error.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = 0;
fd = mkfifo("./fifo", 0600);
if(fd < 0) {
perror("mkfifo error");
//return -1;
}
fd = open("./fifo", O_WRONLY);
char msg[200] = {0};
while(1)
{
memset(msg, 0, sizeof(msg));
sprintf(msg, "abcdefg");
write(fd, msg, sizeof(msg));
printf("msg:%s\n", msg);
sleep(2);
}
return 0;
}
|
mknod
用于创建文件
1
2
3
4
5
6
| #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int mknod(const char *pathname, mode_t mode, dev_t dev);
|
参数:
- pathname: 创建的文件名称
- mode: 文件类型
- dev: 表示该文件对应的设备文件的设备号。只有当文件类型为 S_IFCHR 或 S_IFBLK 的时候该文件才有设备号,创建普通文件时传入0即可。
该函数最主要的用途就是创建FIFO文件,比如你的代码:
1
| mknod(FIFO_FILE,S_IFIFO|0666,0);
|
FIFO_FILE是一个字符指针,指向文件名,S_IFIFO表示要创建一个FIFO文件,0666表示该文件的权限是所有人可读可写,0表示该文件不是一个设备文件。