在Linux/Unix系统中,进程间通信方式(Inter-Process Comunication)通常有如下若干中方式:
这些通信机制统称IPC,它们各有特色,各有适用的场合。
不管是匿名管道还是具名管道,在Linux系统下都属于文件的范畴,区别是匿名管道没有名称,因此无法使用open创建或打开,事实上匿名管道有自己独特的创建接口,但其读写方式与普通的文件一样,支持read()/write()操作。
管道文件事实上还包括网络编程中的核心概念套接字,所谓的管道指的是这些文件不能进行“定位”,只能顺序对其读写数据,就像一根水管,拧开水龙头不断读取,就可以源源不断读到水管中的数据,但如果没有水出来那只能继续等待,不能试图“跳过”部分文件去读写水管的中间地带,这是管道的最基本的特性。
水管
创建匿名管道的函数接口非常简单,如下所示:
#include
int pipe( int fd[2] );
总结一句话,匿名管道适用于一对一的、具有亲缘关系的进程间的通信。
下面以父子进程使用匿名管道通信的例子对PIPE的使用加以说明,假设父进程先创建一条匿名管道,然后产生一个子进程,此时子进程自然继承了这条管道的读写端描述符,进而它们就可以通信了。
父子进程使用匿名管道通信
示例代码如下:
#include
#include
#include
#include
#include
int main(void)
{
// 创建匿名管道
int fd[2];
pipe(fd);
// 子进程
if(fork() == 0)
{
// 向父进程打招呼
char *msg = "hello parent!";
write(fd[1], msg, strlen(msg));
exit(0);
}
// 父进程
else
{
char buf[50];
bzero(buf, 50);
// 静静地等待子进程的消息
read(fd[0], buf, 50);
printf("来自子进程: %s\n", buf);
exit(0);
}
}
注意,匿名管道的读写端是严格区分的,任何不规范的操作都是不允许的,其结果都是不确定的。
另外还应该注意到,一般而言,不需要用到的文件描述符都最好及时关闭,避免不必要的副作用或浪费系统资源。例如上述程序中,子进程只用到了管道的写端,因此它的fd[0]可以也应该要关闭,相反父进程只用到了管道的读端,因此它的fd[1]可以也应该关闭。
代码可以改成:
int main(void)
{
// 创建匿名管道
int fd[2];
pipe(fd);
// 子进程
if(fork() == 0)
{
// 关掉不必要的读端
close(fd[0]);
...
}
// 父进程
else
{
// 关掉不必要的写端
close(fd[1]);
...
}
}
当我们对一个管道文件(包括匿名管道、具名管道和网络socket)进行读写操作时,我们需要知道将会发生什么,比如读一个空管道会怎么样?对一个缓冲区已满的管道执行写入操作会怎么样等等,可以对这些读写操作做一个统一的整理。
注意,所谓的读者、写者不是只正在读或者正在写的进程,而是只要拥有读写权限就称为管道的读者写者,比如如下进程关闭了匿名管道的读端,因此它只能称为匿名管道的写者:
// 创建匿名管道
int fd[2];
pipe(fd);
// 关闭读端,剩下写端
close(fd[0]);
又如下面这个进程,使用读写权限打开了具名管道,因此该进程既是读者也是写者:
int fd = open("fifo", O_RDWR);
下面是读写特性对照表:
仔细看管道读写特性的表会发现,当试图读取一个空管道,或者试图写入一个缓冲区已满的管道时,读写操作默认会进入所谓“阻塞(se)”的状态。所谓的阻塞实际上就是系统将该进程挂起,等待资源就绪再继续调度的一种状态,这种阻塞的状态有利于系统中别的进程可高效地使用闲置CPU资源,提高系统的吞吐量。
对于阻塞而言,有如下特性需要记忆:
以下是设置管道文件阻塞特性的代码:
#include
#include
#include
#include
#include
int main()
{
// 管道默认为阻塞
int fd[2];
pipe(fd);
// 1,将管道设置为非阻塞
long flag = fcntl(fd[0], F_GETFL);
flag |= O_NONBLOCK;
fcntl(fd[0], F_SETFL, flag);
int n;
char buf[20];
// 此处,读不到数据将立即返回
n = read(fd[0], buf, 20);
if(n < 0)
perror("read failed");
// 2,将管道重新设置为阻塞
flag = fcntl(fd[0], F_GETFL);
flag &= ~O_NONBLOCK;
fcntl(fd[0], F_SETFL, flag);
// 此处,读不到数据将持续等待
n = read(fd[0], buf, 20);
if(n < 0)
perror("read failed");
return 0;
}
注意:
管道打开时,必须同时有读者和写者,否则 open 也会阻塞。