分享

你知道进程间怎么通信吗?

 黄爸爸好 2021-10-18

进程是相互独立的,他们之间的通信只能通过内核。

一、管道

创建管道

匿名管道的创建,需要通过下这个系统调用:
这里表示创建⼀个匿名管道,并返回了两个描述符,

  • ⼀个是管道的读取端描述符 fd[0]
  • ⼀个是管道的写入端描述符 fd[1] 。

这个匿名管道是特殊的文件,只存在于内存,不存于文件系统中。
其实,所谓的管道,就是内核里面的⼀串缓存。从管道的⼀段写入的数据,实际上是缓存在内核中的,另⼀端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。

父子进程的管道单向通信

使用fork创建子进程,创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个fd[0]fd[1],两个进程就可以通过各自的 fd 写入和读取同⼀个管道文件实现跨进程通信了。
在这里插入图片描述

父子间的双向通信管道

如果需要双向通信,则应该创建两个管道。

Shell中的管道通信

在 shell里面执行A | B 命令的时候,A 进程和 B 进程都是 shell 创建出来的子进程,A 和 B 之间不存在父子关系,它俩的父进程都是 shell。
在这里插入图片描述

匿名管道与命名管道

  • 匿名管道:通信范围存在父子关系的进程。因为它没有管道文件,只能通过fork复制父进程的fd文件描述符。
  • 命名管道:对于命名管道,它可以在不相关的进程间也能相互通信。因为命令管道,提前创建了⼀个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。

管道特点

  • 管道传输数据是单向的,如果想相互通信,我们需要创建两个管道。
  • 管道这种通信方式效率低,不适合进程间频繁地交换数据。
  • 简单,同时也我们很容易得知管道⾥的数据已经被另⼀个进程读取了。

二、消息队列

消息队列是保存在内核中的消息链表,在发送数据时,会分成⼀个⼀个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型, 所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。 消息队列⽣命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会⼀直存在,二而前面提到的匿名管道的生命周期,是随进程的创建而建立,随进程的结束而销毁。消息这种模型,两个进程之间的通信就像平时发邮件⼀样,你来⼀封,我回⼀封,可以频繁沟通了

不足

  • 通信不及时
  • 附件有大小限制

三、共享内存

共享内存的机制,就是拿出⼀块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西, 另外⼀个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。
在这里插入图片描述

四、信号量

为了防止进程竞争共享资源,而造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只 能被⼀个进程访问。正好,信号量就实现了这⼀保护机制。
信号量其实是⼀个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。
信号量表示资源的数量,控制信号量的方式有两种原子操作

  • P 操作,这个操作会把信号量减去 1,相减后如果信号量 < 0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量 >= 0,则表明还有资源可使用,进程可正常继续执行
  • V 操作,这个操作会把信号量加上 1,相加后如果信号量 <= 0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量 > 0,则表明当前没有阻塞中的进程

P 操作是用在进⼊共享资源之前,V 操作是⽤在离开共享资源之后,这两个操作是必须成对出现的。

  • 信号初始化为 1 ,就代表着是互斥信号量,它可以保证共享内存在任何时刻只有⼀个进程在访问,这就很好的保护了共享内存
  • 信号初始化为 0 ,就代表着是同步信号量,它可以保证进程 A 应在进程 B 之前执行

五、信号

上面说的进程通信,是常规模式状态下的工作模式。对于异常情况下的工作模式,就需要用信号的方式来通知进程。
信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令)。 信号是进程间通信机制中唯⼀的异步通信机制,因为可以在任何时候发送信号给某⼀进程,⼀旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式

  • 执行默认操作,Linux 对每种信号都规定了默认操作
  • 捕捉信号,我们可以为信号定义⼀个信号处理函数。当信号发生时,我们就执行相应的信号处理函数。
  • 忽略信号,当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILL 和 SEGSTOP ,它们用于在任何时候中断或结束某⼀进程。

六、Socket

管道、消息队列、共享内存、信号量和信号都是在同⼀台主机上进⾏进程间通信,那要想跨网络与不同主机上的进程之间通信,就需要 Socket 通信了。 Socket 通信不仅可以跨⽹络与不同主机的进程间通信,还可以在同主机上进程间通信。

创建Socket的系统调用

int socket(int domain, int type, int protocal)
三个参数分别代表

  • domain:用来代表指定协议族,比如AF_INET用于IPV4、AF_INET6用于IPV6、AF_LOCAL/AF_UNIX用于本机
  • type:用来指定通信特性,比如SOCK_STREAM表示的是字节流,对于TCP、SOCK_DGRAM 表示的是数据报,对应 UDP、SOCK_RAW 表示的是原始套接字
  • protocal:原本是用来指定通信协议的,但现在基本废弃。因为协议已经通过前面两个参数指定完成,protocol目前⼀般写成 0 即可

通信方式

根据创建 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协议通信的Socket编程模型

在这里插入图片描述

  • 服务端和客户端初始化 socket ,得到文件描述符
  • 服务端调用bind ,将绑定在 IP 地址和端口
  • 服务端调用listen ,进行监听
  • 服务端调用accept ,等待客户端连接
  • 客户端调用connect ,向服务器端的地址和端⼝发起连接请求
  • 服务端accept返回用于传输的socket的文件描述符
  • 客户端调用write写入数据
  • 服务端调用read 读取数据
  • 客户端断开连接时,会调用close ,那么服务端read读取数据的时候,就会读取到了EOF ,待处理完数据后,服务端调调用 close ,表示连接关闭。

UDP协议通信的Socket编程模型

在这里插入图片描述
UDP是没有连接的,所以不需要三次握⼿,也就不需要像 TCP调用listen和connect,但是UDP的交互仍然需要 IP 地址和端口号,因此也需要 bind。对于UDP 来说,不需要维护连接,那么也就没有所谓的发送方和接收方,甚⾄都不存在客户端和服务端的概念,只要有⼀个 socket多台机器就可以任意通信,因此每⼀个UDP 的 socket 都需要 bind。另外,每次通信时,调用sendto和recvfrom,都要传入目标主机的 IP地址和端口。

本地进程间通信的Socket编程模型

本地 socket 被用于在同⼀台主机上进程间通信的场景:

  • 本地socket的编接口和 IPv4 、IPv6 套接字编程接口是⼀致的,可以支持字节流和数据报」两种协议
  • 本地 socket 的实现效率大高于 IPv4 和 IPv6 的字节流、数据报 socket 实现
  • 对于本地字节流 socket,其 socket 类型是 AF_LOCAL 和 SOCK_STREAM。 对于本地数据报 socket,其socket 类型是 AF_LOCAL 和 SOCK_DGRAM。 本地字节流 socket 和 本地数据报 socket 在 bind 的时候,不像 TCP 和 UDP 要绑定 IP 地址和端口,而是绑定⼀个本地文件,这也就是它们之间的最大区别。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多