Linux下的进程通信方式: 管道通信详解
管道是单向的、先进先出的、无结构的字节流,它把一个进程的输出和另一个进程的输入连接在一起。
写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。
管道提供了简单的流控制机制。进程试图读一个空管道时,在数据写入管道前,进程将一直阻塞。同样,管道已经满时,进程再试图写管道,在其它进程从管道中读走数据之前,写进程将一直阻塞。
匿名管道具有的特点:
只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。
一种半双工的通信模式,具有固定的读端和写端。
LINUX把管道看作是一种文件,采用文件管理的方法对管道进行管理,对于它的读写也可以使用普通的read()和write()等函数。但是它不是普通的文件,并不属于其他任何文件系统,只存在于内核的内存空间中。
1.管道创建与关闭说明
管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fds[0]和fds[1]:
fds[0]固定用于读管道;
fd[1]固定用于写管道;
管道关闭时只需将这两个文件描述符关闭即可,可使用普通的close()函数逐个关闭各个文件描述符。
2.管道创建函数
创建管道可以通过调用pipe()来实现:
3.管道读写说明
使用管道进行父子进程间通信的步骤:
创建管道:父进程调用pipe()函数创建一个管道
此时,管道的读端和写端都在一个进程之中,这种管道是没有多大用的。
父进程通过fork()函数创建一子进程
子进程会继承父进程所创建的管道,这时,父子进程中管道的文件描述符对应关系如图8.4所示。
确定管道的传输方向:在父、子进程中根据需要的传输方向关闭无关的读端或写端文件描述符
通信:在写进程中调用write()函数,在读进程中调用read()函数
关闭管道:调用close()关闭管道相关的文件描述符。
图1:
图2:
图3:
关闭了父进程的写端fd[1]和子进程的读端fd[0],这样就可以建立一条“子进程写入父进程读取”的通道。
也可以关闭父进程的读端fd[0]和子进程的写端fd[1],这样就可以建立一条“父进程写入子进程读取”的通道。
父进程还可以创建多个子进程,各个子进程都继承了相应的fd[0]和fd[1],这时,只需要关闭相应端口就可以建立起各子进程(兄弟进程)之间的通道。
4.管道使用实例
在本例中,首先创建管道,之后父进程使用fork()函数创建子进程,然后通过关闭父进程的读描述符和子进程的写描述符,建立起它们之间的管道通信。
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_DATA_LEN 256
#define DELAY_TIME 1
int main()
{
pid_t pid;
int pipe_fd[2];
char buf[MAX_DATA_LEN];
const char data[] = "Pipe Test Program";
int real_read, real_write;
memset((void*)buf, 0, sizeof(buf));
/* 创建管道 */
if (pipe(pipe_fd) < 0)
{
printf("pipe create error\n");
exit(1);
}
if ((pid = fork()) == 0)
{
/* 子进程关闭写描述符,并通过使子进程暂停3s等待父进程已关闭相应的读描述符 */
close(pipe_fd[1]);
sleep(DELAY_TIME * 3);
/* 子进程读取管道内容 */
if ((real_read = read(pipe_fd[0], buf, MAX_DATA_LEN)) > 0)
{
printf("%d bytes read from the pipe is '%s'\n", real_read, buf);
}
/* 关闭子进程读描述符 */
close(pipe_fd[0]);
exit(0);
}
else if (pid > 0)
{
/* 父进程关闭读描述符,并通过使父进程暂停1s等待子进程已关闭相应的写描述符 */
close(pipe_fd[0]);
sleep(DELAY_TIME);
if((real_write = write(pipe_fd[1], data, strlen(data))) != -1)
{
printf("Parent wrote %d bytes : '%s'\n", real_write, data);
}
close(pipe_fd[1]); /*关闭父进程写描述符*/
waitpid(pid, NULL, 0); /*收集子进程退出信息*/
exit(0);
}
return 0;
}
运行结果:
5.管道读写注意点
只有在管道的读端存在时,向管道写入数据才有意义。否则,向管道写入数据的进程将收到内核传来的SIGPIPE信号(通常为Broken pipe错误)。
向管道写入数据时,Linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。
父子进程在运行时,它们的先后次序并不能保证,因此,在这里为了保证父子进程已经关闭了相应的文件描述符,可在两个进程中调用sleep()函数,这不是很好的解决方法,应尽可能使用进程之间的同步与互斥机制解决父子进程之间的同步问题。
匿名管道的局限性:
只支持单向数据流
只能用于具有亲缘关系的进程之间通信,没有名字
缓冲区有限,管道只存在于主存中,大小为一个页面
所传送的是无格式字节流
出处:https://blog.****.net/rl529014/article/details/51464363