前言
1、首先是管道,管道的缺点是通信效率差,只能单向,而且不能随机读取数据
2、解决管道的问题,可以使用消息队列,消息队列可以随机读取,比较灵活,但是消息队列其实是存放在内核中的,数据拷贝涉及到用
户态和内核态的切换,且大数据消息效率比较差。
3、为了解决消息队列频繁切换上下文的情况,可以使用共享内存的方式,减少数据的拷贝
4、共享内存会带来多进程的安全问题,可以使用信号量的方式来屏蔽这个问题
5、以上的方式都是单机多进程之间的通信,如果的跨机器进程间通信如何实现呢,socket!
1. 管道|匿名管道
1.1 有名管道
创建管道:mkfifo name
一个线程向管道输入数据 (如果没有进程取出数据,则一直阻塞):
echo 'ibli' > name
另一个线程取出管道的数据:
cat < name
对于命名管道,它可以在不相关的进程间也能相互通信。因为命令管道,提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。
1.2 匿名管道
存储在内存中的特殊文件ps -ef | grep zookeeper
对于匿名管道,它的通信范围是存在父子关系的进程。因为管道没有实体,也就是没有管道文件,只能通过 fork 来复制父进程 fd 文件描述符,来达到通信的目的。
1.3 管道的缺点
1、通信方式效率很差,不适合进程间频繁的交换数据。
2、管道只能一端写入,另一段读出
1.4 管道的优点
1、实现简单,操作简单
2、很容易知道管道的数据已经被另一个进程读取(阻塞特性)
2. 消息队列
消息队列存放在内核中,只有在内核重启也就是操作系统重启或者显示的删除一个消息队列时,该消息队列才会被真正的删除。
和管道不同的是,消息队列在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。
消息队列可以实现消息的随机查询,消息不一定按照先进先出的次序读取,也可以按照消息的类型读取,比FIFO更加灵活。
2.1 消息队列缺点
1、进程间通信可能不及时。
2、**消息队列不适合比较大数据的传输**,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度
也是有上限。在 Linux 内核中,会有两个宏定义 MSGMAX
和 MSGMNB
,它们以字节为单位,分别定义了一条消息的最大长度和一个队列的
最大长度。
3、**消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销**,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数
据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。
3. 共享内存
上面👆消息队列存在一个问题就是数据拷贝会有用户态到内核态的互相切换,这个会有性能开销,解决这个问题,可以使用** 共享内存 **
现代操作系统,对于内存管理,采用的是** 虚拟内存技术 **,也就是每个进程都有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不
同的 **物理内存 **中。所以,即使进程 A 和 进程 B 的虚拟地址是一样的,其实访问的是不同的物理内存地址,对于数据的增删查改互不影
响。这就完成了内存共享机制了。
4. 信号量
为了防止多进程竞争共享资源,而造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问。正好,信
号量就实现了这一保护机制。
4.1 什么是信号量
信号量的本质就是一个计数器,用来实现进程之间的互斥与同步。例如信号量的初始值是 1,然后 a 进程来访问内存1的时候,我们就把
信号量的值设为 0,然后进程b 也要来访问内存1的时候,看到信号量的值为 0 就知道已经有进程在访问内存1了,这个时候进程 b 就会访
问不了内存1。所以说,信号量也是进程之间的一种通信方式。
4.2 信号量分类
Linux环境中,有三种类型:
1、Posix(可移植性操作系统接口)有名信号量(使用Posix IPC名字标识)
2、Posix基于内存的信号量(存放在共享内存区中)
3、System V信号量(在内核中维护)
这三种信号量都可用于进程间或线程间的同步。
4.3 信号量原理
信号量表示资源的数量,控制信号量的方式有两种原子操作:
- 一个是 P 操作,这个操作会把信号量减去 -1,相减后如果信号量 < 0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量 >= 0,则表明还有资源可使用,进程可正常继续执行。
- 另一个是 V 操作,这个操作会把信号量加上 1,相加后如果信号量 <= 0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量 > 0,则表明当前没有阻塞中的进程;
P 操作是用在进入共享资源之前,V 操作是用在离开共享资源之后,这两个操作是必须成对出现的。
5. Socket
上面我们说的共享内存、管道、信号量、消息队列,他们都是多个进程在一台主机之间的通信,那两个相隔几千里的进程能够进行通信吗?
答是必须的,这个时候 Socket 这家伙就派上用场了,例如我们平时通过浏览器发起一个 http 请求,然后服务器给你返回对应的数据,这
种就是采用 Socket 的通信方式了。
套接字特性
套接字的特性由3个属性确定,它们分别是:域、端口号、协议类型。
(1)套接字的域
它指定套接字通信中使用的网络介质,最常见的套接字域有两种:
一是AF_INET,它指的是Internet网络。
当客户使用套接字进行跨网络的连接时,它就需要用到服务器计算机的IP地址和端口来指定一台联网机器上的某个特定服务,所以在使用
socket作为通信的终点,服务器应用程序必须在开始通信之前绑定一个端口,服务器在指定的端口等待客户的连接。
另一个域AF_UNIX,表示UNIX文件系统。
它就是文件输入/输出,而它的地址就是文件名。
(2)套接字的端口号
每一个基于TCP/IP网络通讯的程序(进程)都被赋予了唯一的端口和端口号,端口是一个信息缓冲区,用于保留Socket中的输入/输出信
息,端口号是一个16位无符号整数,范围是0-65535,以区别主机上的每一个程序(端口号就像房屋中的房间号),低于256的端口号保
留给标准应用程序,比如pop3的端口号就是110,每一个套接字都组合进了IP地址、端口,这样形成的整体就可以区别每一个套接字。
(3)套接字协议类型
因特网提供三种通信机制,
一是流套接字,
流套接字在域中通过TCP/IP连接实现,同时也是AF_UNIX中常用的套接字类型。流套接字提供的是一个有序、可靠、双向字节流的连接,
因此发送的数据可以确保不会丢失、重复或乱序到达,而且它还有一定的出错后重新发送的机制。
二个是数据报套接字,
它不需要建立连接和维持一个连接,它们在域中通常是通过UDP/IP协议实现的。它对可以发送的数据的长度有限制,数据报作为一个单
独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP不是一个可靠的协议,但是它的速度比较高,因为它并一需要总是要建立和
维持一个连接。
三是原始套接字,
原始套接字允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因
为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层
和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以
用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW。