目录
1、进程间通信介绍
进程通信的目的
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态。
2、管道
什么是管道
管道式Unix中最古老的进程间通信的形式
我们把从一个进程到另一个进程的一个数据流称为一个"管道"
3、匿名管道
#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码
用fork来共享管道原理
父进程对于管道的读写打开之后,fork出子进程,子进程是对父进程内容的一份拷贝,因此子进程对于管道的通道也是打开的,这就使父进程与子进程通过管道连接在了一起。
这里的管道其实属于半双工通信(下面介绍特点会讲),因此只能让一端读,一端写,所以上图还有点小缺陷,这里我们选择父进程读,子进程写,改进后的图如下:
像这样有着亲子关系连接的管道就是匿名管道。
代码实例
cpp
//实现父进程向子进程输入"i am father"
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/wait.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <sys/types.h>
7
8 int main()
9 {
10 int fd[2] = {0};
11 if(pipe(fd) < 0)
12 {
13 perror("pipe");
14 exit(-1);
15 }
16
17 pid_t id = fork();
18 if (id == 0)
19 {
20 //child
21 close(fd[1]); //子进程关闭写
22 char buffer[64];
23 while(1)
24 {
25 ssize_t s = read(fd[0], buffer, sizeof(buffer));
26 if(s > 0)
27 {
28 buffer[s] = '\0';
29 printf("father send to child: %s\n", buffer);
30 }
31 else if (s == 0)
32 {
33 printf("read file end!\n");
34 break;
35 }
36 else
37 {
38 printf("read error!\n");
39 break;
40 }
41 }
42 }
43 //father
44 close(fd[0]); //父进程关闭读
45 const char* msg = "i am father";
46 write(fd[1], msg, strlen(msg));
47
48 waitpid(id, NULL, 0);
49
50 return 0;
51 }
匿名管道特点
智能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
管道提供流式服务
一般而言,进程退出,管道释放,所以管道的生命周期随进程
一般而言,内核会对管道操作进行同步于互斥
管道式半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
匿名管道的读写规则
1、当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到由数据来到为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
2、当管道满的时候
O_NONBLOCK disable:write调用阻塞,直到有进程读走数据。
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN。
3、如果所有管道写端对应的文件描述符被关闭,则read返回0。
4、如果管道读端对应的描述文件符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出。
5、当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
6、当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
4、命名管道
管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道式一种特殊类型的文件。
创建一个命名管道
命名管道可以从命令行上创建,命令行方法是使用下面这个指令:
$ mkfifo filename
再用一段代码来测试一下命名管道的功能
可以看到,我们在第一个终端执行命令向fifo中写入,第二个终端执行cat命令从fifo中读取,这两个命令的进程是没有关系的
这里需要注意的是,如果先关闭了读进程,那么写就没有意义,写的进程会被直接kill掉。
命名管道也可以从程序里创建,相关的函数为:
int mkfifo(const char *filename, mode_t mode);
创建命名管道:
int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}
匿名管道与命名管道的区别
匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开用open。
FIFO与pipe之间唯一的区别在于它们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义。
代码实例
cpp//comm.h #pragma once #include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define FILE_NAME "myfifo"
cpp//server.c #include "comm.h" int main() { if(mkfifo(FILE_NAME, 0644) < 0) { perror("myfifo"); return 1; } int fd = open(FILE_NAME, O_RDONLY); if(fd < 0) { perror("open"); return 2; } char msg[128]; while(1) { msg[0] = 0; ssize_t s = read(fd, msg, sizeof(msg)-1); if(s > 0) { msg[s] = 0; printf("client# %s\n", msg); } else if(s == 0) { printf("client quit!\n"); break; } else { printf("read error!\n"); break; } close(fd); return 0; } }
cpp//client.c #include "comm.h" int main() { int fd = open(FILE_NAME, O_WRONLY); if(fd < 0) { perror("open"); return 1; } char msg[128]; while(1) { msg[0] = 0; printf("Please Enter$ "); fflush(stdout); ssize_t s = read(0, msg, sizeof(msg)); if(s > 0) { msg[s] = 0; write(fd, msg, strlen(msg)); } } close(fd); return 0; }