紧接昨晚的利用消息队列进行进程间通信,今天讲讲利用管道进行进程间通信。
在Linux中,管道通信是一种使用非常频繁的通信机制。管道在本质上也是一种文件,但是又有区别于一般的文件,因为管道可以克服使用文件会出现的问题。
管道实际上是一个固定大小的缓冲区,该缓冲区的大小为1页,即4K字节,使得它的大小不像文件那样不加检验地增长。同时使用单个固定缓冲区会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据。
首先阐述一下管道的特点:
1、管道是半双工的,即数据只能向一个方向流动,如果需要实现双方通信时,就需要建立两个管道;
2、只能用于父子进程之间或者兄弟进程间这些有亲缘关系的进程;
3、单独构成一种独立的文件系统。因为管道对于管道两端的进程而言,就是一个文件,但是又区别于普通文件,不属于某种文件系统,并且只存于内存中;
4、数据的读写:一个进程向管道中写的内容被管道另一端的进程读取。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读取数据。
管道用于不同进程间的通信,分为无名管道和有名管道。利用管道通信,通常首先创建一个管道,再通过fork()函数创建一个子进程,该子进程会继承父进程所创建的管道。
无名管道的建立:
pipe函数
功能:建立无名管道
头文件:#include <unistd.h>
原型:int pipe(int fileds[2])
说明:参数fileds返回文件描述符,fileds[0]是管道的读取端,fileds[1]是管道的写入端
返回值:成功,返回0,否则返回-1,并将错误原因存于errno中。
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pipe_fd[2];
if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}
else
printf("pipe create success\n");
close(pipe_fd[0]);
close(pipe_fd[1]);
}
调试结果:
pipe create success
注意:必须在调用fork之前调用pipe(),否则子进程不能继承文件描述符
读写无名管道:
管道的两端只能固定地读写,如果试图从管道写端读写数据,或者向管道读端写入数据都会导致错误发生。同时,一般的文件的I/O函数都可以用于管道,例如close、read、write等。
从管道中读取数据:
1、如果写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0.
2、当管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的字节数;如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数,返回请求的字节数。
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int pipe_fd[2];
pid_t pid;
char buf_r[100];
char* p_wbuf;
int r_num;
memset(buf_r,0,sizeof(buf_r));
if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}
if((pid=fork())==0)
{
printf("\n");
close(pipe_fd[1]);
sleep(2);
if((r_num=read(pipe_fd[0],buf_r,100))>0)
{
printf( "%d numbers read from the pipe is %s\n",r_num,buf_r);
}
close(pipe_fd[0]);
exit(0);
}
else if(pid>0)
{
close(pipe_fd[0]);
if(write(pipe_fd[1],"Hello",5)!=-1)
printf("parent write1 Hello!\n");
if(write(pipe_fd[1]," Pipe",5)!=-1)
printf("parent write2 Pipe!\n");
close(pipe_fd[1]);
sleep(3);
waitpid(pid,NULL,0);
exit(0);
}
return 0;
}
调试结果:
parent write1 Hello!
parent write2 Pipe!
10 numbers read from the pipe is Hello Pipe
注意:管道写端关闭后,写入的数据将一直都存在,直到全部读出为止。
向管道中写入数据时,Linux不会保证写入的原子性,管道的缓冲区一有空闲区域,写进程就会试图向管道写入数据,如果读进程不读走管道缓冲区的数据,那么写操作将一直处于阻塞。