管道通讯
1. 管道简介
管道是Linux中进程间通讯的一种方式,它把一个程序的输出直接连接到另一个程序的输入。管道主要包括两种:无名管道和有名管道
无名管道 | 有名管道 |
---|---|
Linux中管道通讯的一种原始方法 | 对无名管道的一种改进 |
只能用于具有亲缘关系的进程之间通讯 | 可以使互不相关的两个进程实现彼此通讯 |
单工的通讯模式,有固定的读端和写端 | 打开管道时可指定读写方式 |
可使用read()/write(),不属于文件系统只存在于内存中 | 通过文件IO操作,内容存放在内存中 |
无名管道和有名管道
2. 无名管道
2.1 无名管道创建与关闭
无名管道时基于文件描述符的通讯方式。当一个管道建立时,它会创建两个文件描述符: fd[0] 和 fd[1],其中fd[0]固定用于读管道,fd[1]固定用于写管道。从而构成了一个单向的数据通道。
创建管道可以通过 pipe()来实现
/*****pipe()函数*****/
函数原型:int pipe(int fd[])
传 入 值:fd[]是包含两个元素的数组,存放管道对应的文件描述符
返 回 值:成功返回0;失败返回-1
管道关闭时只需用 close() 函数将两个文件描述符关闭即可
2.2 无名管道读写
用pipe()函数创建的管道两端处于一个进程中。由于管道主要是用于不同进程间的通讯,通常先是创建一个管道,再调用 fork() 函数创建一个子进程,该子进程会继承父进程的管道。这时父子进程管道的文件描述符对应关系如下左图所示。
由于无名管道时单工的工作模式,即进程要么只能读管道,要么只能写管道。因此只能使用其中一个(例如可约定父进程读管道,子进程写管道)。这样就应该把不使用的读端或写端的文件描述符关闭。如下右图所示,父进程的写端和子进程的读端关闭,建立了一条“子进程写入父进程读取”的管道。
管道读写注意点
- 只有在管道的读端存在时,向管道写入数据才有意义。否则,向管道写入数据的进程将收到内核传来的SIGPIPE信号
- 向管道写入数据时,Linux将不保证写入的原子性,管道缓冲只要有空间,写进程就会试图向管道写入数据。如果管道缓冲区已满,那么写操作将会一直阻塞
- 父子进程在运行时,它们的先后次序并不能保证。为确保父子进程以及关闭了相应的文件描述符,可在两个进程中调用sleep()函数
实例程序
/*****pipe.c*****/
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX_DATA_LEN 256
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(buf,0,sizeof(buf));
if(pipe(pipe_fd) < 0){ //创建管道
perror("fail to pipe");
exit(-1);
}
if((pid = fork()) == 0){ //创建子进程
/*子进程关闭写描述符,并通过是子进程暂停1s等待父进程关闭相应的读描述符*/
close(pipe_fd[1]);
sleep(1);
/*子进程读取管道内容*/
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){
close(pipe_fd[0]); //父进程关闭读描述符
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);
}
}
编译并执行后,结果如下
linux@linux-virtual-machine:~/andy/proc$ ./pipe
parent wrote 17 bytes:'Pipe test program'
17 bytes read from the pipe is 'Pipe test program'
3. 有名管道
3.1 有名管道的创建
有名管道(FIFO)可以使用 mkfifo() 函数来创建,该函数类似于文件中的 open() 操作,可以指定管道的路径和访问权限。管道创建成功后,就可以使用 open()、read() 和 write() 这些函数了。与普通文件一样,对于为读而打开的管道可以在 open() 中设置 O_RDONLY,对于为写而打开的管道可在 open() 中设置 O_WRONLY
/*****mkfifo()函数*****/
函数原型:int mkfifo(const char *filename, mode_t mode)
传 入 值:filename 要创建的管道名
mode 管道的访问权限
返 回 值:成功返回0;失败返回-1
FIFO出错信息 | 含义 |
---|---|
EACCESS | 参数filename所指定的目录路径无可执行的权限 |
EEXIST | 参数filename所指定的文件已存在 |
ENAMETOOLONG | 参数filename的路径名称太长 |
ENOENT | 参数filename包含的文件不存在 |
ENOSPC | 文件系统的剩余空间不足 |
ENOTDIR | 参数filename路径中的目录存在但却非真正的目录 |
EROFS | 参数filename指定的文件存在于只读文件系统内 |
实例程序
/*****fifo_write.c*****/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#define MYFIFO "/tmp/myfifo" //有名管道文件名
int main(int argc, char *argv[]){ //参数为即将写入的字符串
int fd;
int nwrite;
if(argc < 2){
printf("Usage: ./fifo_write string\n");
exit(-1);
}
if((fd = open(MYFIFO,O_WRONLY)) < 0){ //以只写方式打开FIFO管道
perror("fail to open fifo");
exit(-1);
}
if((nwrite = write(fd,argv[1],strlen(argv[1]+1))) >0) //向管道写入字符串
printf("Write '%s' to FIFO\n",argv[1]);
close(fd);
return 0;
}
/*****fifo_read.c*****/
/*头文件和宏定义同fifo_write.c*/
int main(){
char buf[256];
int fd, nread;
/*判断有名管道是否已经存在,若为创建则以相应的权限创建*/
if(access(MYFIFO,F_OK) == -1){ //管道文件不存在
perror("fail to mkfifo");
exit(-1);
}
if((fd = open(MYFIFO,O_RDONLY)) < 0){
perror("fail to open fifo");
exit(-1);
}
while(1){
memset(buf,0,sizeof(buf));
if((nread = read(fd,buf,256)) > 0)
printf("Read '%s' from FIFO\n",buf);
}
close(fd);
return 0
}
把以上两个程序分别在两个终端运行。先启动读管道程序,读进程在建立管道后就开始循环地从管道里读出内容。如果当前管道中没有数据,则一直阻塞到写管道进程向管道写入数据。在运行了写管道程序后,读进程能够从管道读出用户的输入内容,程序运行结果如下
终端一如下
linux@linux-virtual-machine:~/andy/proc$ ./fifo_read
Read 'FIFO' from FIFO
Read 'Test' from FIFO
终端二如下
linux@linux-virtual-machine:~/andy/proc$ ./fifo_write FIFO
Write 'FIFO' to FIFO
linux@linux-virtual-machine:~/andy/proc$ ./fifo_write Test
Write 'Test' to FIFO
关注我的公众号,共同交流学习嵌入式开发相关技术: