一、管道的概念
管道的实质是一个内核缓冲区,进程以先进先出(FIFO, First In First Out)的方式从缓冲区存取数据:管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺序地读取数据,该缓冲区可以看做一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后再缓冲区都不复存在了。管道也可以看成一个文件,但不是普通文件,也不属于任何文件系统,自立门户,构成一个的文件系统,存在于内存中。
管道的局限性:
1.半双工,数据只能在一个方向流动;
2.管道只能在具有共同的父进程之间的两个进程使用;
管道的创建:
管道可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。管道是通过调用pipe函数创建的。
pipe()函数
#include <unistd.h>
int pipe(int fd[2]); //返回值:若成功,返回0,若出错,返回-1.
参数fd返回的两个文件描述符:fd[0]为读而打开,fd[1]为写而打开,fd[1]的输出是fd[0]的输入。
以下演示管道图示:
进程先调用pipe,创建管道,接着调用fork,从而创建了父进程和子进程的IPC通道
如果我们想从父进程传输数据到子进程,父进程关闭读端fd0,子进程断开写端fd1。
代码演示
下面编写一个程序,是父进程将给子进程发数据:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define SENTENCE "Parent say that:hello, pig child"
int main(int argc, char **argv)
{
int pipe_fd[2];
int pid;
int rd;
int wstatus;
char buf[1024];
if( pipe(pipe_fd) < 0)
{
printf("create pipe failure: %s\n", strerror(errno));
return -1;
}
if( (pid = fork()) < 0)
{
printf("create child failure: %s\n", strerror(errno));
return -2;
}
else if(pid == 0)
{
close(pipe_fd[1]);
memset(buf, 0 ,sizeof(buf));
rd = read(pipe_fd[0], buf, sizeof(buf));
if( rd < 0)
{
printf("child read from pipe failure: %s\n", strerror(errno));
return -3;
}
printf("child read %d btyes data from pipe: %s\n", rd, buf);
return 0;
}
close(pipe_fd[0]);
if(write(pipe_fd[1], SENTENCE, strlen(SENTENCE)) < 0)
{
printf("parent write data to pipe failure: %s\n", strerror(errno));
return -4;
}
printf("parent waiting child to exit...\n");
wait(&wstatus);
return 0;
}
首先父进程创建管道之后fork(),这时子进程会继承父进程所有打开的文件描述符(包括管道),这时对于一个管道就有4个读写端(父子进程各有一对管道读写端),如果需要父进程往子进程里写数据,则需要在父进程中关闭读端,在子进程中关闭写端;而如果需要子进程往父进程中写数据,则可以在父进程关闭写端,然后子进程中关闭读端。
程序运行结果:
二、命名管道(fifo)
前面讲到的未命名的管道只能在两个具有亲缘关系的进程之间通信,通过命名管道(Named PiPe)FIFO,不相关的进程也能交换数据。FIFO不同于管道之处在于它提供一个路径与之关联,以FIFO的文件形式存在于系统中。它在磁盘上有对应的节点,但没有数据块——换言之,只是拥有一个名字和相应的访问权限,通过mknode()系统调用或者mkfifo()函数来建立的。一旦建
立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。当不再被进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。
图示:
下面这个例程创建了两个掩藏的命名管道文件(.fifo_chat1和.fifo_chat2)在不同的进程间进行双向通信。该程序需要运行两次(即两个进程),其中进程0(mode=0)从标准输入里读入数据后通过命名管道2(.fifo_chat2)写入数据给进程1(mode=1);而进程1(mode=1)则从标准输入里读入数据后通过命名管道1(.fifo_chat1)写给进程0。
代码示例
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/select.h>
#include <stdlib.h>
#include <fcntl.h>
#include <libgen.h>
#define FIFO1 ".fifo_chat1"
#define FIFO2 ".fifo_chat2"
int stop = 0;
void signal_pipe(int signum)
{
if(SIGPIPE == signum)
{
printf("progrom exit and pipe had broken\n");
stop = 1;
}
}
int main(int argc, char **argv)
{
int fdr;
int fdw;
int rd;
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\n");
return -1;
}
mode = atoi(argv[1]);
if( access(FIFO1, F_OK) )
{
printf("\"%s\" not exist and create it\n", FIFO1);
mkfifo(FIFO1, 0666);
}
if( access(FIFO2, F_OK) )
{
printf("\"%s\" not exist and create it\n", FIFO2);
mkfifo(FIFO2, 0666);
}
signal(SIGPIPE, signal_pipe);
if( mode == 0)
{
if( ( fdr = open(FIFO1, O_RDONLY) ) < 0)
{
printf("open '%s' for read failure: %s\n", FIFO1, strerror(errno));
return -1;
}
if( ( fdw = open(FIFO2, O_WRONLY)) < 0)
{
printf("open '%s' for write failure: %s\n", FIFO2, strerror(errno));
return -2;
}
}
else
{
if( ( fdw = open(FIFO1, O_WRONLY)) < 0)
{
printf("open '%s' for write failure: %s\n", FIFO1, strerror(errno));
return -1;
}
if( ( fdr = open(FIFO2, O_RDONLY)) < 0)
{
printf("open '%s' for read failure: %s\n", FIFO2,strerror(errno));
return -2;
}
}
printf("start chating wtih another program now , please input message now: \n");
while( !stop )
{
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO, &rdset);
FD_SET(fdr, &rdset);
rd=select(fdr+1, &rdset, NULL, NULL, NULL);
if( rd <= 0)
{
printf("time out and create select() failure: %s\n", strerror(errno));
continue;
}
if( FD_ISSET(fdr, &rdset))
{
memset(buf, 0, sizeof(buf));
rd = read(fdr, buf, sizeof(buf));
if(rd < 0)
{
printf("read data from pipe failure: %s\n", strerror(errno));
break;
}
else if( 0==rd )
{
printf("another side of fifo get closed and program will exit\n");
break;
}
printf("<-- %s", buf);
}
if(FD_ISSET(STDIN_FILENO, &rdset))
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
write(fdw, buf, strlen(buf));
}
}
}
程序运行:
管道是一种半双工的通知方式,如果要实现两个进程间的双向通信则需要两个管道,即两个管道分别作为两个进程的读端和写端;
运行如下:
通信结束后按Ctrl+C结束: