linux进程间通信之管道
1、匿名管道
管道是一种最基本的进程间通信机制,管道由pipe函数来创建:
调用pipe函数,会在内核中开辟出一块缓冲区用来进行进程间通信,这块缓冲区称为管道,它有一个读端和一个写端。
pipe函数接受一个参数,是包含两个整数的数组,如果调用成功,会通过pipefd[2]传出给用户程序两个文件描述符,需要注意pipefd [0]指向管道的读端, pipefd [1]指向管道的写端,那么此时这个管道对于用户程序就是一个文件,可以通过read(pipefd [0]);或者write(pipefd [1])进行操作。pipe函数调用成功返回0,否则返回-1..
那么再来看看通过管道进行通信的步骤:
(1)父进程创建管道,得到两个文件描述符指向管道的两端
(2)利用fork函数创建出子进程,则子进程也得到两个文件描述符指向同一管道
(3)父进程关闭读端(pipe[0]),子进程关闭写端pipe[1],则此时父进程可以往管道中进行写操作,子进程可以从管道中读,从而实现了通过管道的进程间通信。
使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK
标志):
-
如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次
read
会返回0,就像读到文件末尾一样。 -
如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次
read
会阻塞,直到管道中有数据可读了才读取数据并返回。 -
如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端
write
,那么该进程会收到信号SIGPIPE
,通常会导致进程异常终止。在第 33 章 信号会讲到怎样使SIGPIPE
信号不终止进程。 -
如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次
write
会阻塞,直到管道中有空位置了才写入数据并返回。
pipe的特点:
-
只能单向通信
-
只能血缘关系的进程进行通信
-
依赖于文件系统
-
生命周期随进程
-
面向字节流的服务
-
管道内部提供了同步机制
测试代码:
/************************************************************************* > File Name: pipe.c > Author:wurong > Mail:1792172013@qq.com > Created Time: Mon 04 Sep 2017 02:13:03 AM PDT ************************************************************************/ #include<stdio.h> #include <string.h> #include <unistd.h> int main() { int _pipe[2]; int ret = pipe(_pipe); if(ret < 0) { printf("pipe fail!!!\n"); return 1; } pid_t id = fork(); if(id < 0) { printf("fork fail!!!\n"); return 2; } else if(id == 0) //子进程 { printf("i am child!!!\n"); close(_pipe[0]); int i = 0; char* msg = NULL; while(i < 100) { msg = "i am child"; write(_pipe[1],msg,strlen(msg)+1); sleep(1); ++i; } } else // 父进程 { close(_pipe[1]); int j = 0; char _msg[100]; while(j < 100) { memset(_msg,'\0',sizeof(_msg)); read(_pipe[0],_msg,sizeof(_msg)); printf("%s\n",_msg); j++; } } return 0; }
> File Name: pipe.c > Author:wurong > Mail:1792172013@qq.com > Created Time: Mon 04 Sep 2017 02:13:03 AM PDT ************************************************************************/ #include<stdio.h> #include <string.h> #include <unistd.h> int main() { int _pipe[2]; int ret = pipe(_pipe); if(ret < 0) { printf("pipe fail!!!\n"); return 1; } pid_t id = fork(); if(id < 0) { printf("fork fail!!!\n"); return 2; } else if(id == 0) //子进程 { printf("i am child!!!\n"); close(_pipe[0]); int i = 0; char* msg = NULL; while(i < 100) { msg = "i am child"; write(_pipe[1],msg,strlen(msg)+1); sleep(1); ++i; } } else // 父进程 { close(_pipe[1]); int j = 0; char _msg[100]; while(j < 100) { memset(_msg,'\0',sizeof(_msg)); read(_pipe[0],_msg,sizeof(_msg)); printf("%s\n",_msg); j++; } } return 0; }
2、命名管道
命名管道也被称为FIFO文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的没有名字的管道(匿名管道)类似。
由于Linux中所有的事物都可被视为文件,所以对命名管道的使用也就变得与文件操作非常的统一,也使它的使用非常方便,同时我们也可以像平常的文件名一样在命令中使用。而且可以在不相关的进程间建立通信关系,效果就如同匿名管道的那样。
我们可以使用两下函数之一来创建一个命名管道,他们的原型如下:
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
int mkfifo(const char *filename, mode_t mode);
-
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);
这两个函数都能创建一个FIFO文件,注意是创建一个真实存在于文件系统中的文件,filename指定了文件名,而mode则指定了文件的读写权限。
mknod是比较老的函数,而使用mkfifo函数更加简单和规范,所以建议在可能的情况下,尽量使用mkfifo而不是mknod。
命名管道创建后就可以使⽤了,命名管道和管道的使⽤⽅法基本是相同的。只是使⽤命名管道时,必须先调⽤open()将其打开。因为命名管道是⼀个存在于硬盘上的⽂件,⽽管道是存在于内存中的特殊⽂件。 需要注意的是,调⽤open()打开命名管道的进程可能会被阻塞。但如果同时⽤读写⽅式(O_RDWR)打开,则⼀定不会导致阻塞;如果以只读⽅式(O_RDONLY)打开,则调⽤open()函数的进程将会被阻塞直到有写⽅打开管道;同样以写⽅式(O_WRONLY)打开也会阻塞直到有读⽅式打开管道。
我们也可创建一个进程向管道里写,另一个从管道里读:
写进程:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define PATH "./file"
int main()
{
umask(0);
int ret = mkfifo(PATH,S_IFIFO | 0666);
if(ret == -1)
{
printf("mkfifo fail!!!\n");
return 1;
}
int fd = open(PATH,O_WRONLY);
if(fd < 0)
{
printf("open fail!!!\n");
return 2;
}
char buf[100];
memset(buf,'\0',sizeof(buf));
while(1)
{
scanf("%s\n",buf);
ret = write(fd,buf,strlen(buf)+1);
if(ret < 0)
{
printf("write fail!!!\n");
return 3;
}
}
close(fd);
return 0;
}
读进程:
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define PATH "./file"
int main()
{
int fd = open(PATH,O_RDONLY);
if(fd <= 0)
{
printf("open fail!!!\n");
return 1;
}
char buf[100];
memset(buf,'\0',sizeof(buf));
while(1)
{
int ret = read(fd,buf,sizeof(buf));
if(ret < 0)
{
printf("read fail!!!\n");
return 2;
}
printf("%s\n",buf);
}
colse(fd);
return 0;
}
写进程:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define PATH "./file"
int main()
{
umask(0);
int ret = mkfifo(PATH,S_IFIFO | 0666);
if(ret == -1)
{
printf("mkfifo fail!!!\n");
return 1;
}
int fd = open(PATH,O_WRONLY);
if(fd < 0)
{
printf("open fail!!!\n");
return 2;
}
char buf[100];
memset(buf,'\0',sizeof(buf));
while(1)
{
scanf("%s\n",buf);
ret = write(fd,buf,strlen(buf)+1);
if(ret < 0)
{
printf("write fail!!!\n");
return 3;
}
}
close(fd);
return 0;
}
读进程:
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define PATH "./file"
int main()
{
int fd = open(PATH,O_RDONLY);
if(fd <= 0)
{
printf("open fail!!!\n");
return 1;
}
char buf[100];
memset(buf,'\0',sizeof(buf));
while(1)
{
int ret = read(fd,buf,sizeof(buf));
if(ret < 0)
{
printf("read fail!!!\n");
return 2;
}
printf("%s\n",buf);
}
colse(fd);
return 0;
}