管道
管道的本质其实就是一个文件,进程可以对其进行读或写,从而实现进程间的通信。
无名管道和有名管道
无名管道
创建单向数据流无名管道代码为:
int pipe(int fd[2]);
该函数返回两个文件描述符:fd[0]用于读,fd[1]用于写。管道被创建与内核态。
管道一个典型而常见的例子:
ls | sort | grep xxx
其中,shell将执行上述步骤,创建三个进程和两个管道,并且将每个管道的读出端复制到相应进程的标准输入,将每个管道的写入端复制到相应进程的标准输出。
管道实现进程间通信,是通过内核运作完成的,读写管道时,数据都要穿过用户-接口。
无名管道只能用于有一个共同祖先进程的各个进程之间,无法在无亲缘关系的两个(或多个)进程间进行通信,通常用于父进程和子进程之间的通信(因为创建管道时,没有返回固定的文件名,注意:这是在不考虑描述符传递的基础上)。
有名管道(FIFO)
FIFO(First In First Out)是一个单向数据流,因为每个FIFO都有一个路径名与之关联,所以没有亲缘关系的进程之间,也可以访问同一个FIFO,从而实现进程间的通信。
创建有名管道代码为:
int mkfifo(const char *pathname, mode_t mode);
内核为管道和FIFO维护一个访问计数器,它的值是访问同一个管道或FIFO的打开着的描述符个数。有了这个计数器,即使某个进程删除了FIFO的一个路径名,但目前仍打开着的描述符却不会被影响,也就是说,不会影响这在使用该FIFO的其他进程。
半双工管道和全双工管道
上面说的pipe
函数创建的是半双工管道(单向数据流),但也有一些系统实现的pipe
函数是创建一个全双工管道(双向数据流)。
全双工管道是由两个半双工管道构成的,fd[1]写入的数据,只能从fd[0]读出;写入fd[0]的数据只能从fd[1]读出。
记录传递:字节流
管道通常以字节流I/O模型来传递信息,字节流I/O模型的特点是:不存在记录边界,读写操作根本不检查数据,系统也不对其进行解释。这些解释和检查工作,都由读写的应用进程负责。
那如果要传递的信息长度可变,且读数据的进程需要知道消息边界,以判定在什么时候读出单条消息?可以有三种解决方法:
使用一个或多个特殊字符来分割消息,如使用换行符,或者回车符后加换行符的方式(http协议中用于分隔文本记录的一种方式);
消息记录头部加上用于表明这条消息长度的固定长度字段,这样就可以不需要分割字符,也免去了前面转移字符的麻烦。
每次连接值传输一个记录,传完就关闭(HTTP1.0用的就是这种方法)。