(一)进程间通讯方式----管道
管道是第一个广泛应用的IPC(进程间通信)手段。
管道特点:
- 管道以文件作为交互的媒介
- 管道分为有名管道和无名管道
- 管道都是半双工通讯
- 管道按照队列的先进先出读取数据
- 管道中的read/write函数都是阻塞的,与普通文件不同
有名管道
- 适用于任意进程之间的通讯
- 要创建一个管道文件作为交互的媒介
- FIFO(管道文件)在磁盘上没有数据块,仅用inode来标识内核中的一条通道,与普通文件不同。文件属性类型标识为p表示FIFO,文件大小为0。各进程可以打开这个文件进行read/write,实际上是在读写内核通道(根本原因在于这个file结构体所指向的read、write函数和常规文件不一样),这样就实现了进程间通信。
- 该管道可以通过路径名来指出,并且在文件系统中是可见的.在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便.
- FIFO严格地遵循先进先出规则,对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾,它们不支持如lseek()等文件定位操作
- FIFO提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中.这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO,不相关的进程也能交换数据.
- FIFO可以一个读端,多个写端;也可以一个写端,多个读端
- FIFO文件数据一旦被读,就“不存在”了
有名管道的创建
//可以通过mkfifo命令创建一个管道文件用于操作
//也可以通过mkfifo()创建有名管道,可以指定管道的路径和打开的模式.mkfifo()函数语法如下:
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *filename,mode_t mode); //pathname管道文件的路径名,mode权限位数字
ps:mkfifo函数前要使用unlink(filename);
eg:unlink("FIFO");
mkfifo("FIFO",0777);
注意:
下面我们来一个有名管道的例子:
执行代码之前先用命令在当前的文件夹创建命名管道文件(也可以自己调用函数实现,在这里我用命令创建。)
write.c
int main()
{
int fd=open("./FIFO",O_WRONLY); //打开当前目录下的FIFO管道文件
assert(fd!=-1);
printf("OPEN FIFO SUCCESS\n");
write(fd,"HELLO",6);
printf("WRITE OVER\n");
close(fd);
return 0;
}
read.c
int main()
{
int fd=open("./FIFO",O_RDONLY);
assert(fd!=-1);
printf("open success\n");
char buff[128]={0};
read(fd,buff,127);
printf("%s",buff);
close(fd);
return 0;
}
无名管道
- 只适用于父子进程之间的通讯
- 并没有管道文件作为交互的媒介,而是在内核中开辟一块缓冲区(ubantu系统上管道大小是64K)并且也是用环形队列来实现,写端是入队,读端是出队操作,因此操作遵循队列原则总是按照先进先出的原则工作。
- 因为其也是一个半双工通讯,所以具有固定的读端和写端
- 管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read(),write()等函数.但是它不是普通的文件,并不属于其他任何文件系统。
-
流式服务。发送和接收大小不受特定格式的限制。
-
管道的生命周期和进程有关。
-
同步与互斥原则
无名管道的创建
管道是基于文件描述符的通信方式,当一个无名管道建立时,它会创建两个文件描述符fds[0]和fds[1],其中fds[0]固定用于读管道,而fd[1]固定用于写管道,这样就构成了一个半双工的通道.管道关闭时只需将这两个文件描述符关闭即可,可使用普通的close()函数逐个关闭各个文件描述符.
//创建管道可以通过调用pipe()来实现
#include<unistd.h>
int pipe(int fds[2]);//fds[2]是管道的两个文件描述符,之后可以直接操作这两个文件描述符,实际上pipe包含创建与打开一体
两个进程通过一个pipe管道只能实现单向通信,所以我们就需要在进行读操作的进程中关闭其写操作的文件描述符,在进行写操作的进程中关闭读操作的文件描述符。举例:子进程读父进程写,就要关闭父进程的读操作文件描述符fds[0],关闭子进程的写操作文件描述符fds[1]。但如果有时候也需要子进程写父进程读,就必须另开一个管道。
如果只开一个管道,但是父进程不关闭读端,子进程也不关闭写端,双方都有读端和写端,为什么不能实现双向通信?
因为俩进程同时读/写,会造成数据混乱,因为读写指针只有一个,但我们并不能保证读写的顺序。
示例代码:
int main()
{
int fds[2];
pipe(fds); //创建并打开管道
pid_t pid=fork();
if(pid==0) //我们这里举例子进程读,父进程写
{
close(fds[1]); //子进程关闭写端
char buff[128]={0};
int n=read(fds[0],buff,127);
printf("%s\n",buff);
close(fds[0]);
}
else
{
close(fds[0]); //父进程关闭读端
write(fds[1],"hello",5);
wait(NULL); //等待子进程结束,以免出现僵尸进程
close(fds[1]);
}
exit(0);
}
无名管道的集中特殊情况:
1:当读一个写端被关闭的管道时,在所有数据被读取完成后,read返回0,以指示达到了文件结束处。
2:当读一个写段未被关闭的管道,但写段也并没有数据写入时,读进程从管道中将数据读完后并不会返回0,而是read阻塞,直到管道中有数据了读取后才会返回
3:当写一个读端被关闭的管道时,则写段进程会收到信号SIGPIPE,通常会导致进程异常终止,如果忽略该信号或者捕捉该信号并从其处理程序返回,则write返回-1。
4:当写一个读端未被关闭的管道,且读端并不会读取管道中的数据,那么写端就一直往管道中写,直到管道写满,write就会阻塞,直到管道中有空位才写入数据并返回。