和单进程程序一样,简单的就是最好的。在使用更复杂、更晚出现的技法前,应该通过实证所有出现更早的,更简单的技法都不管用了
1、管道、重定向和过滤器
管道是对"做单件事情并做好"的哲学理念的践行;
约定:每个程序一开始(至少)有两个I/O数据流可用:从标准输入和标准输出(文件描述符数字分别为0和1)。许多程序都可以写作过滤器,从标准输入顺序读取数据,并且只向标准输出写数据。
通常,这些数据流分别和用户键盘和显示器连接。但是unix shell普遍支持重定向操作,可以把这些标准输入输出流连接到文件。举例如下:
管道把一个程序的标准输出连接 到另一个程序的标准输入。用这种方式连接起来的一系列程序称为管线。
|-----------|--------------------------------------------|
| ls > foo | 把目录命令ls的输出写入到名为"foo"的文件中 |
| wc > foo | 将令数字统计程序wc以文件"foo"为输入,然后把字符数、字数、行数、发送到标准输出 |
| ls | wc | 可以看到当前目录列表的行数 |
管道线中所有阶段的程序都是并发运行的,注意到这一点很重要。每一段等待前一段的输出作为输入,但在下一段能够运行前没有哪个段必须退出。这个特性在分析管道的交互作用时候非常重要。管道和重定向的组合能力非常强大。
管道的缺点:单向性,管道线的成员除了终止外,不可能回传控制信息。管道是半双工通信
- 为分页程序建立管道 ps | more 把ps的输出管道连接至more的输入,每次按键后可以继续显示一整屏的进程列表,more不能向前滚屏,现代unix中已经用ps | less 取代了more来获得更好的 分页程序体验
2、对等进程间的通信
实际上通信和网络中,我们常常用需要对等通道,需要数据能够自由双向的流动。
(1)临时文件:把临时文件作为协作程序之间的通信中转站使用,是最古老的IPC通信方法。
|----------------------|-------------------------------------------------------------------------------------------------|
| 优点 | 缺点 |
| 创建灵活简单,不容易产生死锁和竞争 | 若进程在临时文件可被删除前中断,会遗留垃圾数据;多个实例使用用一个名字作为临时文件会产生冲突,规避方式是shell脚本中对临时文件名称包含$$,用载入shell的进程ID来保证文件名的唯一性 |
| 有时候其他方式派不上用场,只能使用该方式 | 若攻击程序知道临时文件要写入的位置,可覆盖掉那个文件,可以读取数据或者修改、假造数据。如果涉及的进程有root权限,风险更大 |
3、信号
同一个机器上的两个进程相互通信,最简单原始的方法是一个进程向另一个进程发送信号。
4、套接字
封装网络数据的方法。通过套接字通信的2个程序通常都存在双向字节流。字节流既是按序,又是可靠的。
性能压力可能促使使用共享内存、临时文件或者其他要求更多局部性条件的技法,但是现代最好设想代码需要增加分布式操作。套接字是双向IPC的正确方法。优雅的使用套接字首先需要设计套接字之间时用的应用协议------即一套请求和响应能够简洁的表达程序通讯的语义。如TCP/IP风格的套接字,已经成为主流的unix IPC方法
使用套接字通信的2个进程可能在不同的机器上(甚至可能跨越半个帝地球,通过互联网连接起来)。
5、共享内存
共享内存要求生产者和消费者必须在同一硬件上。如果通信进程能够访问同一个物理内存,那么共享内存则是最快的信息传递方法。
由于共享内存无法通过类似读写调用的规范自动序列化,因此处理共享内存的程序必须要自己处理竞争和死锁问题。典型方法是在共享段中使用信号量变量。