Linux进程间的通信
- 00.再说这个问题前,首先应该知道为什么要进程间通信呢?,它带给我们什么东西,致使我们去学习它呢?
1.数据传输:一个进程需要将它的数据发送给另一个进程,比如我再自己的一个网站的项目中,子进程进行程序替换,去执行爬取数据,然后将数据给父进程,然父进程进行展示。
2.事件通知:如当一个进程执行写文件操作后,需要告诉另一个去读文件的进程写操作已经完成了,另一个进程现在可以开始读取文件了。其中异步就是再数据准备好了,也从内核中拷贝到了用户空间了,此时可以都去了。
3.进程控制:当一个进程希望去管理另一个进程,当这个进程出现异常情况时,尝试去重启进程。也能时刻直到了进程的状态。
4.数据共享:用来交换两个进程的数据,实现数据的共享。但是可能要引入一些别的机制来确保其正常的交换(同步互斥)。
- 01.进程间通信–匿名管道
1.什么是管道呢?请看下图
2.看下linux对匿名管道的定义
3.虾面通过一个实例代码,来看一下,我想就可以很好的了解匿名管道
int main()
{
//1.创建管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret<0)
{
perror("pipe");
return -1;
}
//2.从标准输入读取内容放入管道
char buf[1024]= {0};
read(0,buf,sizeof(buf)-1);
//3.写入管道
write(pipefd[1],buf,strlen(buf));
//4.从管道读取内容
memset(buf,0,sizeof(buf)-1);
read(pipefd[0],buf,sizeof(buf)-1);
//5.写到标准输出
std::cout<<buf<<std::endl;
//关闭管道文件
close(pipefd[0]);
close(pipefd[1]);
return 0;
}
//程序执行结果
[skin@localhost ~]$ ./a.out
告诉你们别惹我,我狠起来连自己都杀
告诉你们别惹我,我狠起来连自己都杀
4.由上面的可知管道就是一个文件,一个进程写,一个进程读,从而是实现了"所谓的通信"。
5.上篇博客讲了进程的创建,那么结合上篇博客,利用fork来共享管道,从而实现父子进程的通信。
int main()
{
//1.创建管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret<0)
{
perror("pipe");
return -1;
}
//1.进行fork,创建子进程
char buf[1024]={0};
ret =fork();
if(ret<0)
{
perror("fork");
return -1;
}
else if(ret==0)
{
//2.子进程从标准输入读取内容,写入管道
read(0,buf,sizeof(buf)-1);
write(pipefd[1],buf,strlen(buf));
memset(buf,0,sizeof(buf));
close(pipefd[0]);
close(pipefd[1]);
}
else
{
wait(NULL);
//3.父进程从管道读取内容,写到标准输出
read(pipefd[0],buf,sizeof(buf)-1);
std::cout<<buf<<std::endl;
close(pipefd[0]);
close(pipefd[1]);
}
return 0;
}
//程序执行结果
[skin@localhost ~]$ ./a.out
hello world
hello world
6.上面是再fork之前创建的管道,fork后父子进程共享管道,子进程去写,父进程取读,从而是实现了进程间通信。
7.总结一下匿名管道
a.匿名管道其实就是一个文件,用来两个进程进行沟通的,但是只能用于有亲缘关系的进程一般就是调用fork,完成父子进程间的通信。
b.匿名管道是提供的流式服务,面向字节流的
c.内核对管道内置了同步互斥
d.管道是半双工的,只能一方进行读,一方进行写,不能同时进行读写操作,这样在实际中效率是很差的。要实现全双工需要两个管道来完成。如图
e.管道的生命周期随进程,即进程结束了,管道也被销毁了
- 02.进程间通信–命名管道
1.命名管道
a.命名管道也是一种文件,特殊的文件(FIFO文件) b.可以用于任何两个进程间,不管是否有没有亲缘关系
2.命名管道的创建:
a.命令行创建:[skin@localhost ~]$ mkfifo filename b.程序中创建:mkfifo("./filename",0644);
3.通过写一个简单的server/client来看一下,命名管道的用法
//server.cc
int main()
13 {
14
15 //1.创建管道
16 int ret =mkfifo("./filename",0644);
17 if(ret<0)
18 {
19 perror("mkfifo");
20 return -1;
21 }
22 //打开管道(默认时关闭的)
23 int fd =open("./filename",O_RDWR);
24 if(fd<0)
25 {
26 perror("open");
27 return -1;
28 }
29 char buf[1024]={0};
30 while(1)
31 {
32 ssize_t read_size= read(fd,buf,sizeof(buf)-1);
33 if(read_size<0)
34 {
35 std::cout<<"rend error\n";
36 continue;
37 }
38 else if(read_size>0)
39 {
40 std::cout<<"client say : "<<buf<<std::endl;
41 memset(buf,0,strlen(buf));
42 }
43 }
44 close(fd);
45 return 0;
46 }
//client.cc
int main()
13 {
14
15 //1.打开命名管道
16 int fd =open("./filename",O_RDWR);
17 if(fd<0)
18 {
19 perror("open");
20 return -1;
21 }
22 char buf[1024]={0};
23 while(1)
24 {
25
26 std::cout<<"please input : ";
27 fflush(stdout);
28 ssize_t read_size =read(0,buf,sizeof(buf)-1);
29 if(read_size<0)
30 {
31 perror("read");
32 continue;
33 }
34 else if(read_size>0)
35 {
36 write(fd,buf,strlen(buf));
37 }
38 }
39
40 close(fd);
41
42 return 0;
43 }
程序结果通过两个终端看一下
4.总结一下命名管道
a.管道通信可以不是亲缘关系,热议两个进程都可以的。
b.管道是面试字节流的
c.管道也是半双工的,内核默认提供同步互斥机制。
d.管道是以一个文件的全路径来命名的,默认时关闭的,使用应该手动打开
e.同样的,命名管道的生命周期随进程。
简述一下管道的读写规则:
a.当管道读段关闭,尝试去写,那么OS会默认发送一个SIGPIPE信号杀死进程
b.当管道的写段关闭,尝试去读,那么会直接当作已经读完了,rea返回0
- 03进程间通信–共享内存
1.什么是共享内存?
a.共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。我个人理解就是直接在内存创建一个共享映射区,一旦映射到其他进程的地址空间,那么其也可以访问,省去了拷贝到内核,再通过内核的系统调用来进行访问获取。 b.共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的
2.共享内存示意图:
假设进程1在内存共享映射区放了val=10,那么一旦将这块区域映射到进程2的地址空间,那么进程2也是可以访问到的。
3.共享内存的函数
4.总结一下共享内存
- 04.进程间通信–消息队列
- 05.总结一下
1.所有进程间通信的本质,都是让不同的进程看到一块相同的内存,这样就达成了进程间通信。 2.进程间通信的目的主要是为了完成,数据传输,数据共享,进程控制等。 3.管道相对于IPC来讲,优点是所有的Linux实现都支持, 并且在最后一个访问管道的进程终止后,管道就被完全删除;缺陷是管道只允许单向传输或者用于父子进程之间. 4.IPC的优点是:功能强大,而且可以在任意进程间完成通信,缺陷就是如共享内存,消息队列都是随内核,占用内核资源
如果错误,可以私信,这里表示感谢!