管道是一种两个进程间进行单向通信的机制。因为管道传递数据的单向性,管道又称为半双工管道。管道的这一特点决定了器使用的局限性。管道是Linux支持的最初Unix IPC形式之一,具有以下特点:
1,数据只能由一个进程流向另一个进程(其中一个读管道,一个写管道);如果要进行双工通信,需要建 立两个管道;
2,管道只能用于父子进程或者兄弟进程间通信。,也就是说管道只能用于具有亲缘关系的进程间通信。
POSIX标准中的FIFO又名有名管道或命名管道。我们知道前面讲述的POSIX标准中管道是没有名称的,所以它的最大劣势是只能用于具有亲缘关系的进程间的通信。FIFO最大的特性就是每个FIFO都有一个路径名与之相关联,从而允许无亲缘关系的任意两个进程间通过FIFO进行通信。所以,FIFO的两个特性:
1,和管道一样,FIFO仅提供半双工的数据通信,即只支持单向的数据流;
2,和管道不同的是,FIFO可以支持任意两个进程间的通信。
前面讲到的未命名的管道只能在两个具有亲缘关系的进程之间通信,通过命名管道(Named PiPe)FIFO,不相关的进程也能 交换数据。FIFO不同于管道之处在于它提供一个路径与之关联,以FIFO的文件形式存在于系统中。它在磁盘上有对应的节点,但 没有数据块——换言之,只是拥有一个名字和相应的访问权限,通过mknode()系统调用或者mkfifo()函数来建立的。一旦建 立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。当不再被 进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define F_FILE1 ".fifo_chat1"
#define F_FILE2 ".fifo_chat2"
int g_stop = 0;
void sig_pipe(int signum)
{
if(SIGPIPE == signum)
{
printf("get pipe broken signal and let programe exit\n");
g_stop = 1;
}
}
int main(int argc, char **argv)
{
int fdr_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(F_FILE1 , F_OK) )
{
printf("FIFO file \"%s\" not exist and create it now\n", F_FILE1);
mkfifo(F_FILE1, 0666);
}
if( access(F_FILE2 , F_OK) )
{
printf("FIFO file \"%s\" not exist and create it now\n", F_FILE2);
mkfifo(F_FILE2, 0666);
}
signal(SIGPIPE, sig_pipe);
if( 0 == mode ) // 这里以只读模式打开命名管道FIFO_FILE1的读端,默认是阻塞模式;如果命名管道的写端被不打开则open()将会一直阻塞,以另外一个进程必须首先以写模式打开该文件FIFO_FILE1,否则会出现死锁
{
printf("start open '%s' for read and it will blocked untill write endpoint opened...\n", F_FILE1);
if( (fdr_fifo=open(F_FILE1, O_RDONLY)) < 0 )
{
printf("Open fifo[%s] for chat read endpoint failure: %s\n", F_FILE1, strerror(errno));
return -1; }
printf("start open '%s' for write...\n", F_FILE2);
if( (fdw_fifo=open(F_FILE2, O_WRONLY)) < 0 )
{
printf("Open fifo[%s] for chat write endpoint failure: %s\n", F_FILE2, strerror(errno));
return -1;
}
}
else // 这里以只写模式打开命名管道FIFO_FILE1的写端,默认是阻塞模式;如果命名管道的读端被不打开则open()将会一直阻 塞, 因为前一个进程是先以读模式打开该管道文件的读端,所以这里必须先以写模式打开该文件的写端,否则会出现死锁
{
printf("start open '%s' for write and it will blocked untill read endpoint opened...\n", F_FILE1);
if( (fdw_fifo=open(F_FILE1, O_WRONLY)) < 0 )
{
printf("Open fifo[%s] for chat write endpoint failure: %s\n", F_FILE1, strerror(errno));
return -1;
}
printf("start open '%s' for read...\n", F_FILE2); if( (fdr_fifo=open(F_FILE2, O_RDONLY)) < 0 )
{
printf("Open fifo[%s] for chat read endpoint failure: %s\n", F_FILE2, strerror(errno));
return -1;
}
}
printf("start chating with another program now, please input message now: \n");
while( !g_stop )
{
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO, &rdset);
FD_SET(fdr_fifo, &rdset);
// select多路复用监听标准输入和作为输入的命名管道读端
rv = select(fdr_fifo+1, &rdset, NULL, NULL, NULL);
if( rv <= 0 )
{
printf("Select get timeout or error: %s\n", strerror(errno));
continue;
}
// 如果是作为输入的命名管道上有数据到来则从管道上读入数据并打印到标注输出上
if( FD_ISSET(fdr_fifo, &rdset) )
{
memset(buf, 0, sizeof(buf));
rv=read(fdr_fifo, buf, sizeof(buf));
if( rv < 0)
{
printf("read data from FIFO get errorr: %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));
}
}
}
./a.out 1 接收到“hello world”,输入“me too”,再看./a.out 0会看到接收到“me too”