| 类型 | 无连接 | 可靠 | 流控制 | 记录消息类型 | 优先级 |
|---|---|---|---|---|---|
| 普通PIPE | N | Y | Y | N | |
| 流PIPE | N | Y | Y | N | |
| 命名PIPE(FIFO) | N | Y | Y | N | |
| 消息队列 | N | Y | Y | Y | |
| 信号量 | N | Y | Y | Y | |
| 共享存储 | N | Y | Y | Y | |
| UNIX流SOCKET | N | Y | Y | N | |
| UNIX数据报SOCKET | Y | Y | N | N |
注:无连接: 指无需调用某种形式的OPEN,就有发送消息的能力流控制; 如果系统资源短缺或者不能接收更多消息,则发送进程能进行流量控制 #各种通信方式的比较和优缺点
- 管道:速度慢,容量有限,只有父子进程能通讯,半全双工
- FIFO:任何进程间都能通讯,但速度慢
- 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题,信息的复制需要额外消耗CPU的时间,不适宜于信息量大或操作频繁的场合。
- 信号量:不能传递复杂消息,只能用来同步
- 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
pipe
特点
- 管道是半双工的,数据只能向一个方向传输,如果想双向通信,需要创建两个管道;
- 管道的读写两端进程要有公共的祖先;
- 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中;
- 一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
- 管道的缓冲区大小是有限的;
- 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等。 ##实现机制 管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。
读写规则
当管道空的时候: O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。 O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。 当管道满的时候: O_NONBLOCK disable: write调用阻塞,直到有进程读走数据 O_NONBLOCK enable:调用返回-1,errno值为EAGAIN 如果所有管道写端对应的文件描述符被关闭,则read返回0 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
FIFO:
特点
- 提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中,通过FIFO不相关的进程也能交换数据;
- 严格先进先出,不能使用lseek()方法定位。
打开规则
FIFO比pipe多了一个打开文件的操作
- 如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。
- 如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。 ##读写规则 ###从FIFO读取数据 如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。
- 如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。
- 对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据;另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。
- 读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。
- 如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。
如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操d设置了阻塞标志的写操作
- 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。
- 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
对于没有设置阻塞标志的写操作:
- 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。
- 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;
信号量
信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。我们通常通过信号来解决多个进程对同一资源的访问竞争的问题,使在任一时刻只能有一个执行线程访问代码的临界区域,也可以说它是协调进程间的对同一资源的访问权,也就是用于同步进程的。
消息队列
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
共享存储器
共享存储,就是一块共享的内存区域,它可被多个进程以读或写的形式访问,以达到进程间通信的目的。因为这个通信方式,发送端与接收端不需要来回复制要发送接收的信息,所以共享存储是最快的一种IPC。既然共享存储允许多个进程去访问,那么它必须满足同步与互斥原则,在发送端正在写的时候,接收端不应该去读。通常我们使用信号量或者互斥量来达到这一目的。
网络流SOCKET
TCP 是一种面向连接的字节流套接字,所以服务端需要通过 listen() 转变为被动 socket,通过 accept() 等待连接。
网络数据报SOCKET
UDP是一种无连接的数据报套接字
UNIX流SOCKET
SOCKET是一个特殊的文件,使用方式和SOCKET网络通信一样,只是通过文件路径名传输数据,文件路径名包括不同路径名和抽象路径名。文件路径名使用‘/0’结尾,抽象路径名使用‘/0’开头,所以抽象路径名要有特殊的解析方式。 #UNIX数据报SOCKET 本地SOCKET通信方式的速度很快,因为数据不需要在网络中传输,不使用底层的协议包装数据,在进程间只需要传输应用层数据。UNIX数据报SOCKET是可靠的,既不会丢失数据报也不会传递出错。可以使用面向网络的套接字和socketpair函数创建UNIX套接字。UNIX套接字的作用相当于全双工通道,两端都可以读和写。