参考链接
- 每个进程的 用户地址空间 都是独立的,一般而言是不能互相访问的,但 内核空间 是每个进程都共享的,所以进程之间要通信必须通过内核
本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类:
消息传递(管道、FIFO、消息队列)
同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
共享内存(匿名的和具名的)
远程过程调用(Solaris门和Sun RPC)
管道
- 管道传输数据是 单向 的,如果想相互通信,我们需要创建两个管道才行
- 管道是以 文件 的方式存在
- 管道传输的数据是 无格式的流 且 大小受限
匿名管道
- 用完销毁
- 匿名管道是特殊的文件,只存在于内存(内核里面的一串缓存),不存于文件系统中
- 匿名管道的 生命周期随进程 的创建而建立,随进程的结束而销毁
- 通信范围:存在 父子关系 的进程
- 因为管道没有实体,也就是没有管道文件,只能通过 fork 来复制父进程 fd 文件描述符,来达到通信的目的
命名管道
- FIFO,因为数据是 先进先出 的传输方式
- 管道这种通信方式效率低,不适合进程间频繁地交换数据
- 可以在 不相关的进程间 也能相互通信
- 因为命令管道,提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信
$ mkfifo myPipe //通过 mkfifo 命令来创建,并且指定管道名字
$ echo "hello" > myPipe // 将数据写进管道
$ cat < myPipe // 读取管道里的数据
消息队列
- 消息队列是保存在 内核中的消息链表
- 在发送数据时,会分成一个一个独立的数据单元,也就是 消息体(数据块)
- 消息体是用户 自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是 固定大小 的存储块
- 如果进程从消息队列中读取了消息体,内核就会把这个消息体删除
- 消息队列 生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在
不足
- 不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限
- 消息队列通信过程中,存在用户态与内核态之间的 数据拷贝开销
===========================================================
-
消息队列是分布式系统中重要的组件
-
使用消息队列主要有两点 好处
- 通过
异步处理提高系统性能
(削峰、减少响应所需时间)- 将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务
- 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击
-
降低系统耦合性
- 利用 发布-订阅模式 工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息
- 通过
-
使用消息队列带来的一些问题
系统可用性降低
: 系统可用性在某种程度上降低- 在加入MQ之前,不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了
系统复杂性提高
: 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题一致性问题
:消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息就会导致数据不一致的情况
共享内存
现代操作系统的内存管理,采用的是 虚拟内存技术
- 每个进程都有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存中
所以,即使进程 A 和 进程 B 的虚拟地址是一样的,其实访问的是不同的物理内存地址,对于数据的增删查改互不影响
================================================
- 共享内存的机制:拿出一块虚拟地址空间来,映射到相同的物理内存中
- 这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度
信号量
- 为了防止多进程竞争共享资源,而造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问
- 信号量主要用于实现 进程间的互斥与同步,而不是用于缓存进程间通信的数据
- 信号量表示资源的数量,控制信号量的方式有两种原子操作:
- P 操作:(在进入共享资源之前)把信号量减去 -1,相减后如果信号量 < 0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量 >= 0,则表明还有资源可使用,进程可正常继续执行
- V 操作:(在离开共享资源之后)这个操作会把信号量加上 1,相加后如果信号量 <= 0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量 > 0,则表明当前没有阻塞中的进程
信号初始化为 1
:互斥信号量
- 可以保证共享内存在任何时刻只有一个进程在访问,这就很好的保护了共享内存
初始化信号量为 0
:同步信号量
- 可以用信号量来实现多进程同步的方式,可以保证进程 A 应在进程 B 之前执行
信号
- 异常情况 下的工作模式,就需要用「信号」的方式来通知进程
- 信号是进程间通信机制中 唯一的异步通信机制,因为可以在任何时候发送信号给某一进程
kill -l 命令
,查看所有的信号- 信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令)
- Ctrl+C 产生 SIGINT 信号,表示终止该进程;
- Ctrl+Z 产生 SIGTSTP 信号,表示停止该进程,但还未结束;
- kill -9 1050 :给 PID 为 1050 的进程发送 SIGKILL 信号,用来立即结束该进程;
Socket
Socket 通信不仅可以 跨网络与不同主机 的进程间通信,还可以在 同主机 上进程间通信
根据 创建 socket 类型 的不同,通信方式 也就不同:
- 实现 TCP 字节流通信:socket 类型是 AF_INET 和 SOCK_STREAM;
- 实现 UDP 数据报通信:socket 类型是 AF_INET 和 SOCK_DGRAM;
- 实现本地进程间通信:「本地字节流 socket 」类型是 AF_LOCAL 和 SOCK_STREAM,「本地数据报 socket 」类型是 AF_LOCAL 和 SOCK_DGRAM。另外,AF_UNIX 和 AF_LOCAL 是等价的,所以 AF_UNIX 也属于本地 socket;
三种通信方式的编程模式
TCP
服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据
UDP
- UDP 是没有连接的,所以不需要三次握手,也就不需要像 TCP 调用 listen 和 connect,但是 UDP 的交互仍然需要 IP 地址和端口号,因此也需要 bind
- 每一个 UDP 的 socket 都需要 bind
- 每次通信时,调用 sendto 和 recvfrom,都要传入目标主机的 IP 地址和端口