一、引言
进程是内存分配的最小单位,所以每个进程之间的数据都是隔离的,但是有时候进程之间又需要通信,这时候就需要用到进程通信技术。
进程之间通信主要有几种技术:管道、消息队列、共享内存、信号量、信号、套接字、内存映射和远程过程调用。
二、管道
管道分为有名管道和无名管道。管道都是半双工通信,如果需要实现全双工通信需要使用两个管道。
shell
中的ls -l | grep string
中的|
其实也是一种管道。
2.1 无名管道
无名管道主要用于具有亲缘关系的进程之间传递消息。无名管道实质上是内核的一块缓存。
函数原型:
1
2
3
4
#include <unistd.h>
// filedes[0] 和 filedes[1] 分别是管道的两端。
int pipe(int filedes[2]);
无名管道通常使用 fork
函数传递。
代码:
pipe3.c
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
#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 30
int main(int argc, char *argv[]) {
int fds1[2], fds2[2];
char str1[] = "Who are you?";
char str2[] = "Thank you for your message";
char buf[BUF_SIZE];
pid_t pid;
pipe(fds1), pipe(fds2);
pid = fork();
if (pid == 0) {
write(fds1[1], str1, sizeof(str1));
read(fds2[0], buf, BUF_SIZE);
printf("Child proc output: %s \n", buf);
} else {
read(fds1[0], buf, BUF_SIZE);
printf("Parent proc output: %s \n", buf);
write(fds2[1], str2, sizeof(str2));
sleep(3);
}
return 0;
}
2.2 命名管道
命名管道是一个文件,遵循先进先出原则。
函数原型:
1
2
3
4
5
#include <sys/types.h>
#include <sys/stat.h>
// path 是创建的管道全名(包括路径),mode 是管道的读写权限,成功返回 0, 失败返回 -1
int mkfifo(const char *path, mode_t mode);
命名管道和无名管道的使用方法法基本是相同的。只是使用命名管道时,必须先调用open()
将其打开。因为命名管道是一个存在于硬盘上的文件,而无名管道是存在于内存中的特殊文件。
但是调用open()打开命名管道的进程可能会被阻塞。
- 但如果同时用读写方式( O_RDWR)打开,则一定不会导致阻塞;
- 如果以只读方式( O_RDONLY)打开,则调用open()函数的进程将会被阻塞直到有写方打开管道;
- 同样以写方式( O_WRONLY)打开也会阻塞直到有读方式打开管道。
代码:
server.c
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
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
int main()
{
umask(0);//将权限清0
if (mkfifo("./mypipe",0666|S_IFIFO) < 0) {//创建管道
perror("mkfifo");
return 1;
}
int fd = open("./mypipe",O_RDONLY);//打开管道
if(fd < 0){
perror("open");
return 2;
}
char buf[1024];
while (true) {
buf[0] = 0;
printf("wait...\n");
ssize_t s = read(fd,buf,sizeof(buf)-1);
if (s > 0) {
buf[s-1] = 0;//过滤\n
printf("server: %s\n",buf);
} else if (s == 0) {//当客户端退出时,read返回0
printf("The client exit.\n");
break;
}
}
close(fd);
return 0;
}
client.c
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
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
int main()
{
int fd = open("./mypipe",O_WRONLY);//打开管道
if (fd < 0) {
perror("open");
return 1;
}
char buf[1024];
while (true) {
printf("Client: ");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);//向管道文件中写数据
if (s > 0) {
buf[s] = 0;//以字符串的形式写
write(fd,buf,strlen(buf));
}
}
close(fd);
return 0;
}
代码中使用了固定的命名管道的名字,也可以通过传参的方式指定命名管道的名字。