管道(pipe)的概念
管道:管道是UNIX系统IPC的最古老的形式,所有的UNIX系统都提供此种通信机制。管道的实质是一个内核缓冲区,进程以先进
先出(FIFO, First In First Out)的方式从缓冲区存取数据:管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺序地读取数据。
管道的特点
1、它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
2、它只能用于具有亲缘关系的进程之间的通信(父子进程或者兄弟进程之间)
3、它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
管道函数
pipe()
#include <unistd.h>
int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1
注释:调用pipe函数时,首先在内核开辟一块缓冲区用于通信,它有一个读端和一个写端,然后通过fd参数传出给用户进程两个文件描述符,fd[0]指向管道的读端,fd[1]指向管道的写段。用户层面看来,打开管道就是打开了一个文件,通过read()或者write()向文件内读写数据,读写数据的实质也就是往内核缓冲区读写数据。
管道用法示例
1、单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道
2、fork之后做什么取决于我们想要的数据流的方向,对于从父进程到子进程,父进程关闭管道的读端fd[0],子进程关闭写端fd[1]。
3、关闭管道的一端
(1)当读一个写端被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束;
(2)当写一个读端被关闭的管道时,则产生信号SIGPIPE,如果忽略该信号或者捕捉该信号并从其处理程序返回,则
wirte返回-1.
下面编写一个示例,用于父进程给子进程方向发送数据,代码如下
/*********************************************************************************
* Copyright: (C) 2020 makun<[email protected]>
* All rights reserved.
*
* Filename: pipe.c
* Description: This file
*
* Version: 1.0.0(2020年03月18日)
* Author: makun <[email protected]>
* ChangeLog: 1, Release initial version on "2020年03月18日 17时05分31秒"
*
********************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MSG_STR "hello child"
int main (int argc, char **argv)
{
int fd[2];
int rv;
pid_t pid;
char buf[512];
int wstatus;
if(pipe(fd) < 0)
{
printf("create pipe failure: %s\n",strerror(errno));
return -1;
}
printf("打印成功后的返回值\n");
if( (pid=fork()) < 0)
{
printf("create fork failure %s\n",strerror(errno));
return -2;
}
else if(pid ==0 )
{
close(fd[1]);//关闭子进程的写端
memset(buf , 0, sizeof(buf));
rv=read(fd[0],buf,sizeof(buf));//把读到的数据写入buf
if( rv < 0)
{
printf("read from fd[0] failure %s\n",strerror(errno));
return -3;
}
printf("read from fd[0] successfuly and read %d bytes\n", rv);
return 0;
}
else if( pid > 0)
{
close(fd[0]);//关闭父进程的读端
if( write(fd[1],MSG_STR,strlen(MSG_STR)) < 0)
{
printf("write fd[1] from MSG_STR failure%s\n", strerror(errno));
return -4;
}
printf("parent start wait child process exit..\n");
wait(&wstatus);
return 0;
}
}
运行结果:
命名管道(FIFO)的概念
未命名的管道只能具有血缘关心的进程间通信,如果对于任意两个进程我们该怎么通信那,这个时候就提出来了命名管道。
命名管道(named pipe 或 FIFO):任意两个不相关的进程也能交换数据。FFIFO不同于管道之处在于它提供一个路径与之关联,以FIFO的文件形式存在于系统中。它在磁盘上有对应的节点,但没有数据。
命名管道的特点
1、FIFO可以在无关的进程之间交换数据,与无名管道不同。
2、FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
3、遵循先进先出的原则,即第一个进来的数据会第一个被读走。
命名管道函数
#include <sys/stat.h>
// 返回值:成功返回0,出错返回-1
int mknod(const char* path, mode_t mod, dev_t dev);
int mkfifo(const char *path, mode_t mod);
注释:这两个函数都能创建一个FIFO文件,该文件是真实存在于文件系统中的。函数 mknod 中参数 path 为创建命名管道的全路径; mod 为创建命名管道的模式,指的是其存取权限; dev为设备值,改值取决于文件创建的种类,它只在创建设备文件是才会用到。
其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
-
若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。
-
若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。
命名管道用法示例
下面这个例程创建了两个掩藏的命名管道文件(.fifo_chat1和.fifo_chat2)在不同的进程间进行双向通信。
该程序需要运行两次(即两个进程),其中进程0(mode=0)从标准输入里读入数据后通过命名管道2(.fifo_chat2)写入数据给进程1(mode=1);而进程1(mode=1)则从标准输入里读入数据后通过命名管道1(.fifo_chat1)写给进程0,这样使用命名管道实现了一个进程间聊天的程序。
代码示例:
/*********************************************************************************
* Copyright: (C) 2020 makun<[email protected]>
* All rights reserved.
*
* Filename: fifo_chat.c
* Description: This file
*
* Version: 1.0.0(2020年03月20日)
* Author: makun <[email protected]>
* ChangeLog: 1, Release initial version on "2020年03月20日 10时09分55秒"
*
********************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <libgen.h>
#include <stdlib.h>
#define FIFO_FILE1 ".fifo_chat1"
#define FIFO_FILE2 ".fifo_chat2"
int stop = 0;
void sig_pipe(int signum)
{
if(SIGPIPE == signum)
{
printf("get pipe broken signal and let programe exit\n");
stop = 1;
}
}
int main (int argc, char **argv)
{
int fd_fifo;
int fdw_fifo;
int rv;
fd_set rdset;
char buf[1024];
int mode =0;
if( argc != 2 )
{
printf("Usage: %s [0/1]\n", basename(argv[0]));
printf("This chat program need run twice, 1st time run with [0] and 2nd time with [1]\n");
return -1;
}
mode =atoi(argv[1]);
if( access(FIFO_FILE1,F_OK) )//access函数的作用检查.fifo_chat1文件是否存在
{
printf("FIFO file %s not exit and create\n", FIFO_FILE1);
mkfifo(FIFO_FILE1,0666);//创建管道.fifo_chat1是一个普通的路径名,它是该GIGI的名字
}
if( access(FIFO_FILE2,F_OK) )
{
printf("FIFO file %s not exit and create\n", FIFO_FILE2);
mkfifo(FIFO_FILE2,0666);
}
/* 管道是一种半双工的通信方式,什么是半双工的通信,只能从一端写,另一端读,而全双工两端都能读和写,我们可以创建两个管道来实现全双工的通信*/
signal(SIGPIPE,sig_pipe);//安装管道破裂信号,默认动作是程序终止
/* 这里以只读模式打开命名管道FIFO_FILE1的读端,默认是阻塞模式;如果命名管道的写端被不打开则open()将会一直阻 塞,
所以另外一个进程必须首先以写模式打开该文件FIFO_FILE1,否则会出现死锁 */
if( 0 == mode )
{
printf("start open %s for read and it will blocked untill write endpoint ppened..\n",FIFO_FILE1);
if( (fd_fifo=open(FIFO_FILE1, O_RDONLY)) <0)
{
printf("open fifo %s for chat read endpoint failure\n",strerror(errno));
return -1;
}
printf("start open %s for write...\n",FIFO_FILE2);
if( (fdw_fifo=open(FIFO_FILE2,O_WRONLY)) < 0)
{
printf("open fifo %s for chat write endpoint failure\n",strerror(errno));
return -2;
}
}
/*这里以只写模式打开命名管道FIFO_FILE1的写端,默认是阻塞模式。如果命名管道的读端不被打开open讲会一直阻塞因为前一个进程是先以读模式打开该管道文件的读端,所以这里必须先以写模式打开该文件的写端,否则会出现死锁*/
else
{
printf("start open '%s' for write and it will blocked untill read endpoint opened...\n",FIFO_FILE1);
if( (fdw_fifo=open(FIFO_FILE1, O_WRONLY)) <0 )
{
printf("open fifo %s for chat write endpoint failure\n", strerror(errno));
return -3;
}
printf("start open %s for read..\n",FIFO_FILE2);
if( (fd_fifo=open(FIFO_FILE2,O_RDONLY)) <0 )
{
printf("open fifo %s for chat read endpoint failure\n", strerror(errno));
return -4;
}
}
printf("start chating with another program now, please input message now: \n");
while( !stop)
{
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO,&rdset);
FD_SET(fd_fifo,&rdset);
//select多路复用监听标准输入和作为输入的命名管道读端
rv =select(fd_fifo+1,&rdset,NULL,NULL,NULL);
if( rv <= 0)
{
printf("select timeout or errno:%s\n",strerror(errno));
continue;
}
//如果是作为输入的命名管道上有数据到来则从管道上读入数据并打印到标注输出上
if(FD_ISSET(fd_fifo, &rdset) )
{
memset(buf, 0, sizeof(buf));
rv=read(fd_fifo,buf,sizeof(buf));
if( rv <0 )
{
printf("read from fifo failure:%s\n",strerror(errno));
break;
}
else if( 0 == rv )//如果从管道上度到字节数为0,说明管道的写端关闭
{
printf("Another side of FIFO get closed and program will exit now\n");
break;
}
printf("<--%s", buf);
}
//如果标准输入上有数据到来,则从标准输入上读入数据后,讲数据写入到作为输出的命名管道上给另外一个进程
if( FD_ISSET(STDIN_FILENO,&rdset) )
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf),stdin);
write(fdw_fifo,buf,strlen(buf));
}
}
}
代码运行结果: