进程间通信的历史与未来

- START -

我们都知道线程是共享内存空间的,因此不会发生所谓的通信,而进程则存在如何防止多进程同时访问数据的排他控制问题。

5 种进程间通信的方式

  • 管 道
  • SysV IPC
  • TCP 套接字
  • UDP 套接字
  • UNIX 套接字

管道

  所谓管道,就是能够从一侧输入,然后从另一侧读取的文件描述符对。Shell 中的管道也是通过这一方式实现的。

  文件描述符在每个进程中是独立存在的,但创建子进程时会继承父进程中所有的文件描述符,因此它可以用于在具有父子、兄弟关系的进程之间进行通信。

  例如,在具有父子关系的进程之间进行管道通信时,可以按下列步骤操作。在这里为了简单期间,我们只由子进程向父进程进行通信。

  1. 首先,使用 pipe 系统调用,创建一对文件描述符。下面我们将读取一方的文件描述符称为 r,将写入一侧的文件描述符称为 w
  2. 通过 fork 系统调用创建子进程。
  3. 在父进程一方将描述符 w 关闭。
  4. 在子进程一方将描述符 r 关闭。
  5. 在子进程一方将要发送给父进程的数据写入描述符 w
  6. 在父进程一方从描述符 r 中读取数据。

笔者直接上代码演示:

#!/usr/bin/env python
#coding:utf8
import multiprocessing
import time

def process_one(pipe):
    while True:        
        for i in range(3):            
        print "Process-one send: {0}".format(i)
            pipe.send(i)
            time.sleep(1)

def process_two(pipe):
    while True:        
        print "Process-two received: {0}".format(pipe.recv())
        time.sleep(1)
        
# 创建一个管道
pipe = multiprocessing.Pipe()
# pipe 对象:
# (<read-write Connection, handle 5>, <read-write Connection, handle 6>)


p1 = multiprocessing.Process(target=process_one, args=(pipe[0],))
p2 = multiprocessing.Process(target=process_two, args=(pipe[1],))

# 开始子进程
p1.start()
p2.start()

# 等待,直到子进程结束
p1.join()
p2.join()

结果

Process-one send: 0
Process-two received: 0
Process-one send: 1
Process-two received: 1
Process-one send: 2
Process-two received: 2
Process-one send: 0
Process-two received: 0
Process-one send: 1
Process-two received: 1
Process-one send: 2
Process-two received: 2
...

SysV IPC

  UNIX 的 System V (Five) 版本引入了一组称为 SysV IPC 的进程间通信 API,其中 IPC 就是 Inter Process Communication (进程间通信)的缩写。

SysV IPC 包括下列 3 种通信方式。

  • 消息队列
  • 信号量
  • 共享内存

  消息队列是一种用于进程间通信的手段。管道只是一种流机制,每次写入数据的长度等信息是无法保存的,相对的,消息队列则可以保存写入消息的长度。

  信号量(semaphore)是一种带有互斥计数器的标志(flag)。这个词原本是荷兰语「旗语」的意思,在信号量中可以设定对某种「资源」同时访问数量的上限。

  共享内存是一块在进程间共享的内存空间。通过将共享内存空间分配到自身进程内存空间中(attach)的方式来访问。由于对共享内存的访问并没有进行排他控制,因此无法避免一些偶发性问题,必须使用信号量等手段进行保护。

  不过,SysV IPC 有一个资源泄露的问题:由于 SysV IPC 的通信路径能够跨进程访问,因此在使用时需要向操作系统申请分配才能进行通信,通信结束之后还必须显式的销毁,如果忘记销毁的话,就会在操作系统中留下垃圾。

管道之类的方式,则在其所属进程结束的同时会自动销毁,因此比 SysV IPC 要更加易用。

  其次,学习使用新的 API 要花一些精力,但结果也只能用在一台电脑上的进程间通信中,真的让人没什么动力去学呢。

  大家可以在 Linux 中参考一下:

# man svipc

套接字

  System V 所提供的进程间通信手段是 SysV IPC,相对的,BSD 则提供了套接字的方式。和其他进程间通信方式相比,套接字有一些优点:

  • 通信对象不仅限于同一台计算机,或者说套接字本身主要就是为了计算机之间的通信而设计的。
  • (和 SysV IPC 不同)套接字也是一种文件描述符,可进行一般的输入输出。尤其是可以使用 select 系统调用,在通常 I/O 的同时进行「等待」,这一点非常方便。
  • 套接字在进程结束后会由操作系统自动释放,因此无需担心资源泄漏的问题。
  • 套接字(由于其优秀的设计)从很早开始就被吸收进 System V 等系统了,因此在可移植性方面的顾虑较少。

  现在网络几乎完全依赖于套接字。各位所使用的几乎所有的服务的通信都是基于套接字实现的,这样说应该没什么大问题。

套接字分很多种,其中具有代表性的包括:

  • TCP 套接字
  • UDP 套接字
  • UNIX 套接字

  TCP(Transmission Control Protocol,传输控制协议)套接字和 UDP(User Datagram Protocol,用户数据协议)套接字都是建立在 IP(Internet Protocol,网际协议)协议之上的上层网络通信套接字。这两种套接字都可用于以网络为媒介的结算机通信。但它们在性质上有一些区别。

  TCP 套接字是一种基于连接的、具备可靠性的数据流通信套接字。所谓基本连接,是指通信的双方是固定的;而所谓具备可靠性,是指能够侦测数据发送成功或是失败(出错)的状态。

  所谓数据流通信,是指发送的数据是作为字节流来处理的,和通常的输入输出一样,不会保存写入的数据长度信息。

  看了上面的内容,大家可能觉得这些都是理所当然的。我们和 UDP 套接字对比一下,就能够理解其中的区别了。

  UDP 套接字和 TCP 套接字相反,是一种能够无需连接进行通信、但不具备可靠性的数据通信套接字。所谓能够无需连接进行通信,是指无需固定连接到指定对象,可以直接发送数据;不具备可靠性是指可能会出现中途由于网络状况等因素导致发送数据丢失的情况。

  在数据通信中,发送的数据再原则上是能够保存其长度的。但是,在数据过长等情况下,发送的数据可能会被分割。

  先不说无连接通信这一点,UDP 和其他一些性质可能会让大家感到非常难用。这是因为 UDP 几乎是原原本本直接使用了作为其基础的 IP 协议。相反 TCP 为了维持可靠性,在 IP 协议之上构建了各种机制。UDP 的特点是结构简单,对系统产生的负荷也较小。

  因此,在语音通信(如 IP 电话等)中一般使用 UDP,因为通信的性能比数据传输的可靠性要更加重要,也就是说,相比通话中包含少许杂音来说,还是保证较小的通话延迟要更加重要。

  TCP 套接字和 UDP 套接字都是通过 IP 地址和端口号来进行工作的。例如,http 协议中的 http://www.google.com:80/ 就表示与 www.google.com (IP 地址为:31.13.71.7)所代表的计算机的 80 端口建立连接.

UNIX 套接字

  同样是套接字,UNIX 套接字和 TCP、UDP 套接字相比,可以算是一个异类。基于 IP 的套接字一般是通过主机名端口号来识别通信对象的,而 UNIX 套接字则是在 UNIX 文件系统上创建一个特殊文件,并用该文件的路径进行识别。由于这种方式使用的是文件系统,因此大家可以看出,UNIX 套接字只能用于同一台计算机上的进程间通信。

  UNIX 套接字并不是基于 IP 的套接字,它可用于向一台计算机上其他进程提供服务的某种服务程序。

最后

  在进程通信手段中,套接字算是非常好用的,但是即便如此,在考虑对工作进行「委派」时,其易用性还并不理想。套接字本来是为网络服务器的实现而设计的,但作为构建分布式应用程序的手段来说,还是太原始了。

MQ(Message Queue,消息队列)就是为了解决这个问题而诞生的,RPC(Remote Procedure Call,远程过程调用)呢,这一切有没有联系呢?希望本文可以引发你的思考

原文发布于微信公众号 - 随心DevOps(heart-devops)

原文发表时间:2017-12-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏SDNLAB

OpenvSwitch系列之浅析main函数

通过前面几篇解析OpenvSwitch内部主要数据结构和流程,对OpenvSwitch有了相对简单的了解,由于本人不是专业搞OpenvSwitch的,纯属业余爱...

4017
来自专栏Janti

记一次内存溢出的分析经历——thrift带给我的痛orz

说在前面的话 朋友,你经历过部署好的服务突然内存溢出吗? 你经历过没有看过Java虚拟机,来解决内存溢出的痛苦吗? 你经历过一个BUG,百思不得其解,头发一根一...

5208
来自专栏技术记录

rabbitMQ教程(二)一篇文章看懂rabbitMQ

一、rabbitMQ是什么:   RabbitMQ,遵循AMQP协议,由内在高并发的erlanng语言开发,用在实时的对可靠性要求比较高的消息传递上。   学过...

3197
来自专栏13blog.site

Spring+SpringMVC+MyBatis+easyUI整合进阶篇(十四)Redis缓存正确的使用姿势

作者:13 GitHub:https://github.com/ZHENFENG13 版权声明:本文为原创文章,未经允许不得转载。 简介 这是一篇关于...

2955
来自专栏DT乱“码”

Mongdb,Memcached,Redis的使用区别

简介 MongoDB更类似MySQL,支持字段索引、游标操作,其优势在于查询功能比较强大,擅长查询JSON数据,能存储海量数据,但是不支持事务。 Mysql在大...

33310
来自专栏java思维导图

如何设计restful风格接口

URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作。

1302
来自专栏玄魂工作室

Kali Linux Web渗透测试手册(第二版) - 2.3 - 使用Nmap进行扫描和识别应用服务

thr0cyte,Gr33k,花花,MrTools,R1ght0us,7089bAt,

2363
来自专栏应兆康的专栏

Python Web - Flask笔记1

scheme://host:port/path/?query-string=xxx#anchor

1473
来自专栏安富莱嵌入式技术分享

【安富莱】【RL-TCPnet网络教程】第11章 RL-TCPnet调试方法

本章节为大家讲解RL-TCPnet的调试方法,RL-TCPnet的调试功能其实就是通过串口打印实时监控运行状态。而且RL-TCPnet的调试设置比较简单,因为官...

1086
来自专栏北京马哥教育

15个Linux文件传输命令

? 文 | 糖豆 来源 | 菜鸟教程 糖豆贴心提醒,本文阅读时间5分钟,文末有秘密! Linux lprm命令 Linux lprm命令用于将一个工作...

3625

扫码关注云+社区

领取腾讯云代金券