进程间通信之管道
程序员文章站
2022-05-22 19:22:54
...
一. 匿名管道
1. 基本概念
由于没有名字,只能在有共同祖先(有亲缘关系)的进程间使用。管道普遍用于shell中,也可以在程序中实现子进程向父进程回传信息。**实现原理: 内核借助环形队列机制,使用内核缓冲区实现。**管道的大小一般默认为4096。
管道虽然是单个进程创建的,但是却很少在单个进程内使用。其典型用途是为两个不同进程(父子进程)提供进程间通信的手段。当需要一个双向数据流时,我们必须创建两个管道,每个方向一个,步骤如下:
创建管道1(fd1[0]和fd1[1])和管道2(fd2[0]和fd2[1]);
fork创建子进程;
父进程关闭管道1的读 fd1[0] 与 管道2的写fd2[1];
子进程关闭管道1的写 fd1[1] 与 管道2的读fd2[0];
特质: 1. 伪文件
2. 管道中的数据只能一次读取。
3. 数据在管道中,只能单向流动。
局限性 1. 自己写,不能自己读。
2. 数据不可以反复读。
3. 半双工通信。(数据的传输方向是单向的)
4. 血缘关系进程间可用。
2. 匿名管道的使用
pipe函数创建,提供一个单向数据流。
int pipe(int fd[2]);
参数:
fd[0]: 读端,用来读取数据
fd[1]: 写端,用来写入数据
返回值:
成功: 0
失败: -1 errno
管道的读写行为:
读管道:
1. 管道有数据,read返回实际读到的字节数。
2. 管道无数据: 1)无写端,read返回0 (类似读到文件尾)
2)有写端,read阻塞等待。
写管道:
1. 无读端, 异常终止。 (SIGPIPE导致的)
2. 有读端: 1) 管道已满, 阻塞等待
2) 管道未满, 返回写出的字节个数。
代码示例:实现进程间的通信
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<errno.h>
#define MAXLEN 100
void client(int readfd, int writefd);
void server(int readfd, int writefd);
int main(int argc,char* argv[])
{
//定义两个无名管道
int pipe1[2],pipe2[2];
//定义一个子线程
pid_t childpid;
//创建两个无名管道
pipe(pipe1);
pipe(pipe2);
//父进程fock子进程,子进进程也携带两个pipes
if(childpid == fork() == 0)
{
/*子进程关闭pipe1的写 与 pipe2的读*/
close(pipe1[1]);
close(pipe2[0]);
server(pipe1[0],pipe2[1]);/*子进程运行server 程序*/
exit(0);/*子进程终止,变成僵尸进程,*/
}
close(pipe1[0]);/*父进程关闭pipe1的读 与 pipe2的写*/
close(pipe2[1]);
client(pipe2[0],pipe1[1]);/*父进程运行client 程序*/
/*父进程等待子进程结束,取得已终止的子进程(僵尸进程)的终止状态。若无waitpid,则子进程将托孤给Init进程,最终由init进程取得僵尸子进程的状态。*/
waitpid(childpid,NULL,0);
exit(0);
}
/*参数分别为pipe2[0],pipe1[1]*/
void client(int readfd, int writefd)
{
size_t len;
ssize_t n;
char buff[MAXLEN];
/*从标准输入stdin中获取输入(一段路径名)放入buff中*/
fgets(buff, MAXLEN, stdin);
len = strlen(buff);
/*删除由fgets存入的换行符*/
if(buff[len-1] == '\n')
len--;
/*将buff中内容写入管道pipe1[1] writefd,传输到子进程server*/
write(writefd, buff, len);
/*从pipe[0] readfd中读取子进程server传输过来的内容放入buff,并显示到标准输出stdout*/
while((n = read(readfd,buff,MAXLEN)) > 0)
write(STDOUT_FILENO,buff,n);
}
void server(int readfd,int writefd)/*参数对应pipe1[0],pipe2[1]*/
{
int fd;
ssize_t n;
char buff[MAXLEN+1];
/*从管道pipe1[0] readfd中读取来自父进程client的输入并放入buff*/
if((n = read(readfd,buff,MAXLEN)) == 0)
printf("end-of-file while reading pathname\n");
buff[n] = '\n';
/*open 打开client 通过管道传输过来的路径名*/
if((fd = open(buff,O_RDONLY)) < 0)
{
snprintf(buff+n,sizeof(buff) - n,"can not open %s\n",strerror(errno));
n=strlen(buff);
/*打开失败,将错误信息写入管道writefd pipe2[1],传输至父进程client*/
write(writefd,buff,n);
}
else
{
/*打开成功,将路径名对应的文件中的内容读出到buff,并写到管道writefd pipe2[1] 传输至父进程client */
while((n = read(fd,buff,MAXLEN)) > 0)
write(writefd,buff,n);
close(fd);
}
}
3. 设置非阻塞状态
管道默认读写两端都是阻塞状态
设置读端为非阻塞方式
方法:
fcntl-变参函数
1 赋值文件描述符-dup函数
2 修改文件属性
//获取原来的flags
int flags = fcntl(fd[0], F_GETFL);
//设置新的flags
flags |= O_NINBLOCK;
fcntl(fd[0],F_SETFL, flags);
二. 有名管道
1. fifo管道的基本概念
fifo管道:可以用于无血缘关系的进程间通信。FIFO指代先进先出,first in first out,也称为有名管道。其是一个单向的(半双工)的数据流,每个FIFO有一个路径名与之关联,从而实现无亲缘关系的进程访问同一个FIFO。
命名管道: mkfifo
无血缘关系进程间通信:
读端,open fifo O_RDONLY
写端,open fifo O_WRONLY
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);
/*pathname 为文件路径名,是FIFO的名字。mode是文件权限位*/
特点
有名管道
在磁盘上有这样一个文件 ls -l
伪文件,在磁盘上大小永远为0
在内核中有一个对应的缓冲区
半双工的通信方式
FIFO文件可以执行IO函数操作,如open、close、read、write
但是不能执行lseek函数
2. fifo管道实现
示例1:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
void client(int readfd,int writefd);
void server(int readfd,int writefd);
int main(int argc,char *argv[])
{
int readfd,writefd;
pid_t childpid;
/*创建两个FIFO*/
if((mkfifo(FIFO1,FILE_MODE) < 0 ) && (errno != EEXIST))
printf("can not create %s\n",FIFO1);
if((mkfifo(FIFO2,FILE_MODE) < 0 ) && (errno != EEXIST))
{
unlink(FIFO1);
printf("can not create %s\n",FIFO2);
}
if ((childpid = fork()) == 0)/*子进程*/
{
readfd = open(FIFO1,O_RDONLY,0);/*读FIFO1*/
writefd = open(FIFO2,O_WRONLY,0);/*写FIFO2*/
server(feadfd,writefd);
exit(0);
}
/*父进程*/
writefd = open(FIFO1,O_WRONLY,0);/*写FIFO1*/
readfd = open(FIFO2,O_WRONLY,0);/*读FIFO2*/
client(readfd,writefd);
waitpid(childpid,NULL,0);
close(readfd);
close(writefd);
unlink(FIFO1);/*手动删除FIFO1/2*/
unlink(FIFO2);
}
void client(int readfd,int writefd)/*参数分别为 pipe2[0],pipe1[1]*/
{
size_t len;
ssize_t n;
char buff[MAXLEN];
fgets(buff,MAXLEN,stdin);/*从标准输入stdin中获取输入(一段路径名)放入buff*/
len = strlen(buff);
if(buff[len - 1] == '\n') /*删除由fgets存入的换行符*/
len--;
write(writefd,buff,len);/*将buff中内容写入管道pipe1[1] writefd,传输到子进程server*/
while((n = read(readfd,buff,MAXLEN)) > 0)/*从pipe[0] readfd中读取子进程server传输过来的内容放入buff,并显示到标准输出stdout*/
write(STDOUT_FILENO,buff,n);
}
void server(int readfd,int writefd)/*参数对应pipe1[0],pipe2[1]*/
{
int fd;
ssize_t n;
char buff[MAXLEN+1];
if((n = read(readfd,buff,MAXLEN)) == 0)/*从管道pipe1[0] readfd中读取来自父进程client的输入并放入buff*/
printf("end-of-file while reading pathname\n");
buff[n] = '\n';
if((fd = open(buff,O_RDONLY)) < 0)/*open 打开client 通过管道传输过来的路径名*/
{
snprintf(buff+n,sizeof(buff) - n,"can not open %s\n",strerror(errno));
n=strlen(buff);
write(writefd,buff,n);/*打开失败,将错误信息写入管道writefd pipe2[1],传输至父进程client*/
}
else
{
while((n = read(fd,buff,MAXLEN)) > 0)/*打开成功,将路径名对应的文件中的内容读出到buff, 并写到管道writefd pipe2[1] 传输至父进程client */
write(writefd,buff,n);
close(fd);
}
}
示例2:
write_fifo.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
int main(int argc, const char* argv[])
{
if(argc < 2)
{
printf("./a.out fifoname\n");
exit(1);
}
// 判断文件是否存在
int ret = access(argv[1], F_OK);
if(ret == -1)
{
int r = mkfifo(argv[1], 0664);
if(r == -1)
{
perror("mkfifo error");
exit(1);
}
printf("有名管道%s创建成功\n", argv[1]);
}
int fd = open(argv[1], O_WRONLY);
if(fd == -1)
{
perror("open error");
exit(1);
}
char *p = "hello, world";
while(1)
{
sleep(1);
int len = write(fd, p, strlen(p)+1);
}
close(fd);
return 0;
}
read_fifo.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
int main(int argc, const char* argv[])
{
if(argc < 2)
{
printf("./a.out fifoname\n");
exit(1);
}
// 判断文件是否存在
int ret = access(argv[1], F_OK);
if(ret == -1)
{
int r = mkfifo(argv[1], 0664);
if(r == -1)
{
perror("mkfifo error");
exit(1);
}
printf("有名管道%s创建成功\n", argv[1]);
}
int fd = open(argv[1], O_RDONLY);
if(fd == -1)
{
perror("open error");
exit(1);
}
char buf[512];
while(1)
{
int len = read(fd, buf, sizeof(buf));
buf[len] = 0;
printf("buf = %s\n, len = %d", buf, len);
}
close(fd);
return 0;
}
运行结果:
上一篇: imx6ul上mplayer的移植