进程间通信是一个很重要的知识点,现做如下笔记
1 进程间通信
通信方式有如下几种:管道、消息队列、共享内存、信号量、信号、Socket
1.1 管道
- 管道的数据传输是单向的,要么写,要么读,因此要实现进程间互相通信,就需要使用两个管道来实现;
- 命名管道的实质是以文件的形式存在;
- 管道这种通信方式效率很低,不适合进程间频繁的交换数据;
- 所谓的管道,其实就是内核里的一段缓存。因此从管道的一端写入的数据,实际上是缓存在内核中,而另一端读取,也是从这段内核缓存中读取数据;
- 对于匿名管道,它的通信范围是存在于父子进程中;
- 而对于命名管道,它可以在不相关的进程间进行通信,因为管道实质是文件,不相关的进程都可以读取文件。
1.2 消息队列
- 消息队列可解决管道通信效率低的问题,类似于收发邮件:A进程给B进程发信息,A进程把数据放在对应的消息队列后就可以正常返回了,B进程需要的时候再去读取数据即可;
- 消息队列是保存在内核中的消息链表;
- 消息队列通信不及时,并且也有大小限制,这和邮件收发的缺点一样;
- 消息队列不适合比较大数据的传输,因为在内核中,每个消息体都有一个最大长度的限制;
- 消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销,这是因为消息队列是在内核中,进程写入数据时,会发生用户态拷贝数据到内核态的过程;
1.3 共享内存
- 共享内存的方式就能够很好地解决用户态与内核态之间数据拷贝开销的问题。这是因为共享内存的机制就是,拿出一块虚拟地址空间来,映射到相同的物理内存中,因此一个进程向这个物理内存写入的东西,就会被另一个内存所看到,不用拷贝来拷贝去。
- 但共享内存的一大坏处其实就是同步安全性,可能会发生多进程竞争的问题,这是共享数据的通病
1.4 信号量
- 信号量就是解决多进程竞争的问题,提供了一个保护机制,来保护共享资源。信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。就是用于辅助共享内存。
1.5 信号
-
以上的这些通信方式都是常规情况下,当发生异常情况时,就需要用到信号这个东西。注意区分信号与信号量
-
例如对于运行在shell的进程来说,Ctrl+C会产生SIGINT信号,表示终止该进程;Ctrl+Z产生SIGSTP信号,表示挂起该进程,但未结束(此时挂起进程的物理内存会被换出到磁盘当中)
-
kill命令也可以发送信号给进程,最常用的就是结束进程:
kill -9 1050
:-9表示信号为SIGKILL,1050为进程号 -
信号是进程间通信机制中唯一的异步通信机制,因为其可以在任何时候发送信号给某一进程。
1.6 Socket
-
Socket通信就是实现跨网络,与不同主机上的进程之间通信;
-
Socket编程模型如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pB9UlW9r-1653296306862)(C:\Users\PrinceAlLHH\AppData\Roaming\Typora\typora-user-images\image-20220523164846657.png)]
- 服务端与客户端初始化它们各自的socket,得到文件描述符;
- 服务端调用
bind
函数,将初始化号的socket绑定到对应的IP地址与端口上; - 服务器调用
listen
函数,进行监听,设置监听个数; - 服务端调用
accept
函数,等待客户端连接; - 客户端调用
connect
函数,向服务器端的端口和IP地址发起连接请求(accept和connect过程构成三次握手连接) - 服务端
accept
返回一个已完成连接的、用于传输的socket; - 客户端调用
write
写入数据,服务端调用read
读取数据; - 客户端断开连接时,会调用
close
,那么服务器在read
数据的时候,就会读取到EOF
,服务器便知道,再处理完之前的数据之后,服务器也要调用close
来关闭连接。
-
一定要注意,监听的socket和用于传输的socket是两个socket,一个叫做监听socket,一个叫做已完成连接socket。
-
以上是TCP的通信模型,而对于UDP通信来说,由于UDP是面向无连接,因此没有listen、accept、connect过程,因此客户端与服务端在bind好socket之后,就可以进行UDP通信了。
参考文章:
小林coding的《图解系统》