管道:
管道是IPC最基本的一种实现机制。我们都知道在Linux下“一切皆文件”,其实这里的管道就是一个文件。管道实现进程通信就是让两个进程都能访问该文件。而管道又分为匿名管道和命名管道。
匿名管道(piep):
匿名管道的创建:
int pipe(int pipefd[2])调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过pipe参数传出给用户程序两个文件描述符,pipe[0]指向管道的读端,pipe[1]指向管道的写端。所以管道在用户程序看起来就像一个打开的文件,通过read(pipe[0]),或者write(pipe[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。
1、父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
2、父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3、父进程官渡管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里,这样就实现了进程间通信。
#include <stdio.h>管道的特点:
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
if(ret == -1)
{
printf("create pipe eror!");
return 1;
}
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 2;
}
else if(id == 0)
{
close(_pipe[0]);
int i = 0;
char *msg = "My dream is you!";
while(i < 100)
{
write(_pipe[1], msg, strlen(msg)+1);
sleep(1);
i++;
}
}
else
{
close(_pipe[1]);
char msg[100];
memset(msg, '\0', sizeof(msg));
int j = 0;
while(j < 100)
{
ssize_t rec = read(_pipe[0], msg, sizeof(msg));
printf("%s: code is %d\n", msg, rec);
j++;
}
if(waitpid(id, NULL, 0)<0)
return 3;
}
return 0;
}
(1)、单向性
(2)、管道(匿名)只能在具有血缘关系的进程间通信(祖父进程开辟管道,然后fork出子进程和孙子进程他们都有管道的文件描述符从而进行通信)
(3)、管道的生命周期随进程
(4)、管道通信是面向字节流
(5)、自带同步机制
问题:如果只开一个管道,但是父进程不关闭读端,子进程不关闭写端,双方都保留读写端,为什么不能实现双向通信?
答:管道的读写端是通过打开的文件描述符来传递的,因此要通信的两个进程必须从他们的公共祖先那里继承管道的文件描述符。
使用管道的4中特殊情况:
(1)、如果所有指向管道写端的文件描述符都关闭了,而仍然有进程从管道的读端读数据,那么文件内的所有内容被读完后再次read就会返回0,就像读到文件结尾。
(2)、如果有指向管道写端的文件描述符没有关闭(管道写段的引用计数大于0),而持有管道写端的进程没有向管道内写入数据,假如这时有进程从管道读端读数据,那么读完管道内剩余的数据后就会阻塞等待,直到有数据可读才读取数据并返回。
(3)、如果所有指向管道读端的文件描述符都关闭,此时有进程通过写端文件描述符向管道内写数据时,则该进程就会收到SIGPIPE信号,并异常终止。
(4)、如果有指向管道读端的文件描述符没有关闭(管道读端的引用计数大于0),而持有管道读端的进程没有从管道内读数据,假如此时有进程通过管道写段写数据,那么管道被写满后就会被阻塞,直到管道内有空位置后才写入数据并返回。
命名管道(fifo):
管道的一个不足之处是没有名字,因此,只能用于具有亲缘关系的进程间通信,在命名管道(named pipe或FIFO)提出后,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存储于文件系统中。命名管道是一个设备文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。值得注意的是,FIFO(first input first output)总是按照先进先出的原则工作,第一个被写入的数据将首先从管道中读出。
命名管道的创建
#include <sys/stat.h>注释:这两个函数都能创建一个FIFO文件,该文件是真实存在于文件系统中的。函数 mknod 中参数 path 为创建命名管道的全路径; mod 为创建命名管道的模式,指的是其存取权限; dev为设备值,改值取决于文件创建的种类,它只在创建设备文件是才会用到。
int mknod(const char* path, mode_t mod, dev_t dev);
int mkfifo(const char* path, mode_t mod);
返回值:这两个函数都是成功返回 0 ,失败返回 -1
命名管道创建完成后就可以使用,其使用方法与管道一样,区别在于:命名管道使用之前需要使用open()打开。这是因为:命名管道是设备文件,它是存储在硬盘上的,而管道是存在内存中的特殊文件。但是需要注意的是,命名管道调用open()打开有可能会阻塞,但是如果以读写方式(O_RDWR)打开则一定不会阻塞;以只读(O_RDONLY)方式打开时,调用open()的函数会被阻塞直到有数据可读;如果以只写方式(O_WRONLY)打开时同样也会被阻塞,知道有以读方式打开该管道。