进程间通信是指进程间的信息交换,其所交换的信息量少者是一个状态或数值,多者则是成千上万的字节。进程间通信的目的主要是为了实现数据输(一个进程需要将他的数据发给另外一个进程),资源共享(多个进程之间共享同样的资源),通知事件(一个进程需要向另一个或一组进程发送消息,通知他们发生某种事情(如进程终止时要通知父进程)),进程控制。
进程间通信的分类:
管道:匿名管道pipe
命名管道
System V IPC:System V 消息队列
System V 共享内存
System V 信号量
POSIX IPC: 消息队列
共享内存
信号量
互斥量
条件变量
读写锁
一.管道
什么是管道?
我们把一个进程连接到另一个进程的一个数据流程为管道
pipe操作中,读写操作互斥进行。
读进程和写进程同步
匿名管道:
#include<unistd.h>
//功能:创建一个匿名管道
//原型
int pipe(int int fd[2]);
//参数
//fd 文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
//返回值:成功返回0,失败返回错误代码
代码实现一个匿名管道
父进程实现写端,子进程实现读端并将读取的数据打印到屏幕上
1 #include<unistd.h>
2 #include<stdio.h>
3 #include<string.h>
4 #include<stdlib.h>
5 #include<errno.h>
6 int main()
7 {
8 int fd[2];
9 if(pipe(fd)<0)
10 {
11 perror("pipe error");
12 return -1;
13 }
14 int pid =fork();
15 if(pid<0)
16 {
17 return -1;
18 }
19 else if(pid ==0)
20 {
21 //子进程来读数据
22 close(fd[1]);//关闭写端
23 char buf[1024]={0};
24 read(fd[0],buf,1024);
25 printf("%s",buf);
26 close(fd[0]);
27 }
28 else
29 {
30 close(fd[0]);
31 write(fd[1],"hello",5);
32 close(fd[1]);
33 }
34 }
用fork来共享管道原理
1.父进程创建出管道
2.父进程fork出子进程
3.父进程关闭fd[0],子进程关闭fd[1]
管道的读写规则:
当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
当管道满的时候
O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write
进程退出
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
管道的特点
只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程
创建,然后该进程调用fork,此后父子进程之间就可应用该管道。
管道提供流式服务
一般而言,进程退出,管道释放,所以管道的生命周期随进程
一般而言,内核会对管道操作进行同步与互斥
管道是半双工的,数据只能向一个方向流动;需要双方通信时,建立起俩个管道。
命名管道:
管道应用的一个限制就是只能在具有相同祖先的进程间通信。如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。命名管道是一种特殊类型的文件。
1.创建一个命名管道
命名管道可以使用以下指令经行创建
$ mkfifo filename
命名管道也可以使用相关函数进行创建
int mkfifo(const char*filename,mode_t mode);
创建一个命名管道
int main()
{
mkfifo("p2",0644);
return 0;
}
命名管道和匿名管道的区别:
匿名管道由pipe函数创建并打开
命名管道由mkfifo函数创建,打开用open
FIFO与pipe之间唯一的区别在它们创建于打开的方式不同,一旦这些工作完成后,它们具有相同的语意。
命名管道的打开规则:
如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
下面通过代码来实现一个匿名管道
读端
1 /*这是一个命名管道的操作代码,从命名管道中读取数据打印
2 * 1. 创建一个命名管道:
3 * int mkfifo(const char *pathname, mode_t mode);
4 * pathname: 管道文件的路径名
5 * mode: 管道文件的权限
6 * 成功:0 失败:-1
7 * 2. 打开管道 open
8 * 3. 从管道读取数据 read
9 * 4. 不玩了则关闭管道文件close
10 */
11
12 #include <stdio.h>
13 #include <unistd.h>
14 #include <stdlib.h>
15 #include <errno.h>
16 #include <string.h>
17 #include <fcntl.h>
18
19 int main()
20 {
21 umask(0);
22 //为了防止因为管道文件已经存在,每次创建失败
23 //unlink("./test.fifo")----删除文件
24 //1.创建命名管道
25 if(mkfifo("./test.fifo", 0664) < 0) {
26 if (errno == EEXIST) {
27 //代表文件已经存在了
28 //这时候不退出
29 }else {
30 perror("mkfifo error");
31 return -1;
32 }
33 }
34 //2. 打开管道文件
35 //打开特性:
36 // 如果以只读打开命名管道,那么open函数将阻塞等待
37 // 直到有其他进程以写的方式打开这个命名管道
38 //
39 // 如果以只写打开命名管道,那么open函数将阻塞等待
40 // 直到有其他进程以读的方式打开这个命名管道
41 //
42 // 如果命名管道以读写方式打开,则不会阻塞
43 int fd = open("./test.fifo", O_RDONLY);
44 if (fd < 0) {
45 perror("open fifo error");
46 return -1;
47 }
48 printf("open fifo file sucess!! read start!!\n");
49 while(1) {
50 char buff[1024] = {0};
51 int ret = read(fd, buff, 1023);
52 if (ret > 0) {
53 printf("client say:[%s]\n", buff);
54 }else if (ret == 0){
55 //管道特性:如果所有写端被关闭,那么读取时返回0
56 printf("all write point close!!\n");
57 sleep(1);
58 }
59 }
60 close(fd);
61 return 0;
62 }
写端
1 /*这是一个命名管道的操作代码,从命名管道中读取数据打印
2 * 1. 创建一个命名管道:
3 * int mkfifo(const char *pathname, mode_t mode);
4 * pathname: 管道文件的路径名
5 * mode: 管道文件的权限
6 * 成功:0 失败:-1
7 * 2. 打开管道 open
8 * 3. 从管道读取数据 read
9 * 4. 不玩了则关闭管道文件close
10 */
11
12 #include <stdio.h>
13 #include <unistd.h>
14 #include <stdlib.h>
15 #include <errno.h>
16 #include <string.h>
17 #include <fcntl.h>
18
19 int main()
20 {
21 umask(0);
22 //为了防止因为管道文件已经存在,每次创建失败
23 //unlink("./test.fifo")----删除文件
24 //1.创建命名管道
25 if(mkfifo("./test.fifo", 0664) < 0) {
26 if (errno == EEXIST) {
27 //代表文件已经存在了
28 //这时候不退出
29 }else {
30 perror("mkfifo error");
31 return -1;
32 }
33 }
34 //2. 打开管道文件
35 //打开特性:
36 // 如果以只读打开命名管道,那么open函数将阻塞等待
37 // 直到有其他进程以写的方式打开这个命名管道
38 //
39 // 如果以只写打开命名管道,那么open函数将阻塞等待
40 // 直到有其他进程以读的方式打开这个命名管道
41 //
42 // 如果命名管道以读写方式打开,则不会阻塞
43 int fd = open("./test.fifo", O_WRONLY);
44 if (fd < 0) {
45 perror("open fifo error");
46 return -1;
47 }
48 printf("open fifo file sucess!! read start!!\n");
49 while(1) {
50 char buff[1024] = {0};
51 scanf("%s", buff);
52 write(fd, buff, strlen(buff));
53 }
54 close(fd);
55 return 0;
56 }