匿名管道(pipe)
管道是一种最基本的IPC机制,由pipe函数创建。
头文件:#include
函数声明:int pipe( int pipefd[2] )
描述::调用 pipe() 函数时在内核中开辟一块称为管道的缓冲区用于通信。然后通过参数pipefd传出给用户程序两个描述符。pipefd[0] 指向管道的读端,pipefd[1]指向管道的写端。 管道在用户程序看来就像一个打开的文件。通过系统调用read()和write(), 在管道中传输数据。 pipe调用成功返回0,失败则返回-1.
文章的最后面有一些关于进程等待的宏描述。
示意图:
(1):父进程创建管道
(2):父进程调用fork()函数创建出子进程
(3):父进程关闭读端【close( pipefd[0])】,子进程关闭写端【close( pipefd[1])】
1.父进程调用pipe开辟管道,取得两个指向管道的文件描述符。
2.当父进程调用fork 创建子进程时,子进程会把父进程的文件描述符也一同拷贝过去。
3.父进程关闭读端,子进程关闭写端就可以实现父进程向子进程发送数据。。父进程关闭写端,子进程关闭读端。可以实现子进程向父进程写。
4.也就是说数据只能向一个方向流动。需要双向通信时,要建立起两个管道。
正常通信代码:
/*************************************************************************
# > File Name: mypipe.c
# Author: HuoZG
# Created Time: Sat 25 Feb 2017 08:10:36 PM PST
************************************************************************/
#include
#include // pipe() fork()
#include // strlen()
int main()
{
int mypipe[2] = {0, 0};// [0] read [1] write
if(pipe(mypipe) < 0)
{
printf("pipe erroe!\n");
return 1;
}
pid_t id = fork();
if(id < 0)
{
printf("fork error!\n");
return 2;
}
else if (id == 0)
{ // child -> write
close(mypipe[0]); // close read
const char * msg = "Hello World!";
int count = 5;
while(count--)
{
write(mypipe[1], msg, strlen(msg) + 1);
}
close(mypipe[1]);
}
else
{ // father -> read
close(mypipe[1]); // close write
int buf[128];
int count = 5;
while(count--)
{
ssize_t _s = read(mypipe[0], buf, sizeof(buf));
if( _s > 0)
{ // read sucess
printf("%d: child -> father: %s \n", 5-count ,buf);
}
else
{
printf("pipe write is close\n");
break;
}
}
close(mypipe[0]);
}
return 0;
}
1、如果所有指向管道写端的文件描述符都关闭了,而仍然有进程从管道的读端读取数据,那么管道中剩余的数据都被读取后,再次read会返回零。
将代码修改为如下:子进程提前关闭写端,而父进程while(1)一直在读
#include
#include // pipe() fork()
#include // strlen()
int main()
{
int mypipe[2] = {0, 0};// [0] read [1] write
if(pipe(mypipe) < 0)
{
printf("pipe erroe!\n");
return 1;
}
pid_t id = fork();
if(id < 0)
{
printf("fork error!\n");
return 2;
}
else if (id == 0)
{ // child -> write
close(mypipe[0]); // close read
const char * msg = "Hello World!";
int count = 5;
while(count--)
{
write(mypipe[1], msg, strlen(msg) + 1);
if(count == 0)
{
close(mypipe[1]); // 写五条后 关闭文件描述符
}
}
close(mypipe[1]);
}
else
{ // father -> read
close(mypipe[1]); // close write
int buf[128];
int count = 5;
while(1) // 修改为一直读取
{
ssize_t _s = read(mypipe[0], buf, sizeof(buf));
if( _s > 0)
{ // read sucess
printf("%d: child -> father: %s \n", 5-count ,buf);
}
else if (_s == 0)
{
printf(" end of file read return is %d\n", _s);
break;
}
else
{
printf("read error\n");
break;
}
}
close(mypipe[0]);
}
return 0;
}
2、如果指向管道写端的文件描述符没关闭,而 持有管道写端的进程也没有向管道中写数据,这时有进程从管道读取数据,那么管道中剩余的数据都被读取后,再次读取会阻塞。直到管道中有数据可读才读取数据并返回。
/*************************************************************************
# > File Name: mypipe.c
# Author: HuoZG
# Created Time: Sat 25 Feb 2017 08:10:36 PM PST
************************************************************************/
#include
#include // pipe() fork()
#include // strlen()
#include
#include
int main()
{
int mypipe[2] = {0, 0};// [0] read [1] write
if(pipe(mypipe) < 0)
{
printf("pipe erroe!\n");
return 1;
}
pid_t id = fork();
if(id < 0)
{
printf("fork error!\n");
return 2;
}
else if (id == 0)
{ // child -> write
close(mypipe[0]); // close read
const char * msg = "Hello World!";
int count = 5;
while(count--)
{
write(mypipe[1], msg, strlen(msg) + 1);
sleep(1);
if(count == 0)
{
sleep(5); //此时文件描述符没关闭 也不写数据
count = 3; //5s 后读端继续读取
}
}
close(mypipe[1]);
}
else
{ // father -> read
close(mypipe[1]); // close write
int buf[128];
int count = 5;
while(1)
{
ssize_t _s = read(mypipe[0], buf, sizeof(buf));//父进程一直读取 子进程slep时 父进程等待 sleep结束子进程开始写 父进程继续读取
if( _s > 0)
{ // read sucess
printf("%d: child -> father: %s \n", count ,buf);
}
else if (_s == 0)
{
printf(" end of file read return is %d\n", _s);
break;
}
}
}
return 0;
}
打印结果:
打印出前五条后,子进程作为写方sleep停止写入,而父进程此时作为读方,继续等待,知道子进程开始写入。
3、如果所有指向管道读端的文件描述符都被关闭了,这时有进程向管道写,那么该进程会收到信号SIGPIPE,导致进程异常终止。
/*************************************************************************
# > File Name: mypipe.c
# Author: HuoZG
# Created Time: Sat 25 Feb 2017 08:10:36 PM PST
************************************************************************/
#include
#include // pipe() fork()
#include // strlen()
#include
#include
int main()
{
int mypipe[2] = {0, 0};// [0] read [1] write
if(pipe(mypipe) < 0)
{
printf("pipe erroe!\n");
return 1;
}
pid_t id = fork();
if(id < 0)
{
printf("fork error!\n");
return 2;
}
else if (id == 0)
{ // child -> write
close(mypipe[0]); // close read
const char * msg = "Hello World!";
int count = 5;
while(1)
{
write(mypipe[1], msg, strlen(msg) + 1); // 写端一直在写
sleep(1);
}
close(mypipe[1]);
}
else
{ // father -> read
close(mypipe[1]); // close write
int buf[128];
int count = 5;
while(1)
{
count--;
ssize_t _s = read(mypipe[0], buf, sizeof(buf));
if( _s > 0)
{ // read sucess
printf("%d: child -> father: %s \n", count ,buf);
}
else if (_s == 0)
{
printf(" end of file read return is %d\n", _s);
break;
}
if(count == 0)
{
close(mypipe[0]); // 关闭读端
break;
}
}
int status = 0;
ssize_t ret = waitpid(id, &status, 0);
if(ret>0&&WIFSIGNALED(status) ) // 等待子进程 等待成功且子进程异常终止则为真
{
printf("status: %d\n",WTERMSIG(status));// 返回一个信号编号
}
}
return 0;
}
我们可以看到返回13是:SIGPIPE
4、如果指向管道读端的文件描述符没关闭,而持有管道读端的进程也没有从管道中读取数据,这时有进程向管道写端写数据,那么在管道被写满时,再次write会阻塞,直到有空位置才继续写入数据。
几个关于进程等待的宏:
WIFEXITED(status) :若正常终止子进程返回的状态,则为真。 用来查看进程是否正常退出。
WEXITSTATUS(status):若 WIFEXIFED(status)非零,提取子进程退出码。 用来查看进程的退出码。
如果进程不是正常退出,即WIFEXITED返回0,那么这个值就没有意义。
WIFSIGNALED(status):若为子进程异常终止返回状态(收到一个未捕捉的信号),则为真。如上面测试(3)中使用捕捉子进程异常退出信号。
WTERMSIG(status):若WIFSIGNALED(status)非零,则返回一个信号编号,linux可以用kill -l 命令查看信号编号列表。
WIFSTOPPED(status):若子进程意外终止,则为真。
WTOPSIG(status):若WIFSTOPPED为真,则返回一个信号编号。
命名管道(pipe)
概念:管道的一个不足之处是没有名字,只能用于具有亲缘关系的进程间通信,在命名管道提出后,就克服了该限制。命名管道是一个设备文件,因此,及时进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。而且总是按照先进先出的原则,第一次被写入的数据首先从管道中读出。
命名管道的创建:Linux有两种方式创建命名管道。第一种是在Shell下交互地建立一个命名管道,第二种是在程序中使用系统函数建立命名管道。Shell下可以使用命令 mknod或者 mkfifo。系统函数有mknod和mkfifo。
头文件:
#include
#include
定义:
int mknod(const char *path, mode_t mode, dev_t dev);
int mkdifi(const char* path , mode_t mode);
path: 创建命名管道的全路径名
mode:为创建的命名管道的模式
dev : 是设备值,取决于文件创建的种类。
返回值:调用成功返回0,失败返回-1.
使用举例:
umask(0);
if(mknod("./fifo", S_IFIFO|0666) == -1)
{
perror("mknod error");
exit(1);
}
umask(0);
if(mkfifo("./fifo", S_IFIFO|0666) == -1)
{
perror("mkfifo error");
exit(1);
}
"S_IFIFO|0666" 指明创建一个命名管道且存储权限位0666.
管道的几种打开方式:
O_RDWR 读写方式 不会阻塞
O_RDONLY 读模式
O_WRONLY 写模式
一个命名管道的使用实例:
client代码
#include
#include
#include
#include
#include
#include
int main()
{
int _o = open("./fifo",O_WRONLY);
if(_o == -1)
{
perror("open\n");
return 2;
}
char buf[128];
while(1)
{
printf("请输入聊天内容:");
fflush(stdout);
ssize_t _s = read(0, buf, sizeof(buf));
if(_s > 0)
{
buf[_s] = '\0';
write(_o, buf,strlen(buf));
}
}
close(_o);
return 0;
}
#include
#include
#include
#include
#include
#include
int main()
{
umask(0);
int _pipe = mkfifo("./fifo", 0666|S_IFIFO);
if(_pipe < 0)
{
perror("mkfifo\n");
return 1;
}
int _o = open("./fifo",O_RDWR);
//int _o = open("./fifo",O_RDONLY);
if(_o == -1)
{
perror("open\n");
return 2;
}
char buf[128];
while(1)
{// server read
ssize_t _s = read(_o, buf, sizeof(buf));
if(_s > 0)
{
buf[_s-1] = '\0';
printf("client #: %s\n", buf);
}
else if (_s == 0)
{
printf("client quit, I should quit!\n");
break;
}
else
{
perror("read\n");
}
}
close(_o);
return 0;
}
通信成功截图
总结:
1、管道是一个环形队列缓冲区,允许两个进程以生产者消费者模型进程通信。是一个先进先出(FIFO)队列,由一个进程写,而由另一个进程读。
2、管道在创建时获得一个固定大小的字节数。当一个进程试图往管道中写时,如果有足够的空间,则写请求立即被执行,否则该进程被阻塞。如果一个进程试图读取的字节数多于当前管道中的字节数,也将被阻塞。
3、操作系统强制实行互斥,只能有一个进程可以访问管道。
4、只有有血缘关系(父子关系)的进程才可以共享匿名管道,不相关的进程只能共享命名管道。