进程通讯之有名管道
一、前言:
我们前面博客讲过,父子进程之间,对于文件操作的文件操作符是共享的,而对于进程的全局数据,堆区数据,栈区数据是不共享的,那么进程之间到底是怎么进行信息的传递的呢,具体的实现,以及传递的过程是怎么完成的呢,前面我们讲过信号在进程之间的使用,那也是一种进程之间信息的传递。
二、进程的通讯方式---有名管道
进程的通讯方式主要有信号,管道,信号量,消息队列,共享内存,socket(套接字);
而我们今天说的是管道中的有名管道,那么什么是管道呢,在前面的文件系统中说过一个文件的分类中有一个文件类叫管道文件,而我们这边说的管道就是管道文件,为啥呢?Linux下一切皆文件。
接着我们看它的定义:
管道:在内存上开辟一块空间,用文件描述符指向这块空间(也就是以操作文件的方式来使用这块空间)。
有名管道:在文件系统中存在一个文件标识(文件名),但是这管道文件不占据磁盘空间,而是在内存上,需要传递的数据缓存在内存区域上。
管道文件与普通文件的区别:
存储:
管道文件---------------内存
普通文件---------------磁盘
有名管道是一种特殊的管道:称为FIFO(First In First Out,先进先出),类似于我们在数据结构中学的‘队列’,它也叫命名管道,用于没有联系的进程之间的通信。
接着我们看两个无关的进程之间数据传输的图示:
上面是两个进程之间进行信息的传递,利用管道文件,而管道文件所在为内存区域,当进程A对管道文件进行写操作,进程B 就会从管道文件读取进程A写在管道文件中的数据,从而实现两个进程之间数据的传递。
创建管道的命令:
shell下: mkfifo + 所要创建的管道文件名
函数中创建管道文件的函数是:
int mkfifo(const char*pathname, mode_t mode);
管道文件的操作:
打开:int open(char *path,int flag,);
读:int read(int fd, void *buff, size_t size);
写: int write(int fd, void *buff, size_t size);
关闭:int close(int fd);
对于普通文件操作时打开文件,如果这个文件不存在,则创建,但是对于管道文件则不行,则需要调用上面的mkfifo()函数,创建一个管道文件。
三、练习:
利用有名管道使进程A将"Hello World"发送给进程B:
代码如下:
管道文件名为:baby
写操作:文件名:a.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
int main()
{
int fr = open("baby",O_WRONLY);
assert(fr != -1);
printf("open succes\n");
write(fr,"hello world",11);
close(fr);
exit(0);
}
读操作:文件名:b.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
int main()
{
int fr = open("baby",O_RDONLY);
assert(fr != -1);
printf("open succes\n");
char buff[20];
read(fr,buff,15);
printf("buff = %s\n",buff);
exit(0);
}
运行结果:
只运行 ./a的时候
什么也没有打印,而我们在函数内部定义的打开成功则打印"open succes",但是输出面板上并没有,说明open这个函数打开并没有成功。反过来我们看读取文件。
只运行 ./b(读取文件)的时候
也是没有打印和上述现象一模一样,那这到底是什么原因呢。我们接着试一下,两个程序(进程)同时运行时会有什么现象,理想情况下,两个都会打印open succes,b文件也就是读取文件会打印Hello World 。
两个同时运行:打开两个终端,分别运行./a ./b
当./a 命令发出时,没有变化,当./b继续执行时,这时候./a执行的终端下打印出了open succes,说明管道文件打开成功,而./b在它的所在终端下也打印出了open succes,和Hello World 说明一切与预想的一模一样。这又是为什么呢,为什么两个程序单独执行的时候open都没有执行,而当同时执行的时候,两个程序都运行正常呢。
提到这里,它的主要原因是与有名管道的特性分不开的。
open阻塞:
对于./a程序,当单独运行的时候,open以只写方式打开上面所创建的管道文件baby,这时候,open函数会阻塞运行,直到有另外一个进程以只读或者读写的方式去打开管道文件的时候,两个进程才会运行下去。
反之对于./b程序,当程序单独运行且open以只读方式打开所创建的管道文件baby,也会引起阻塞,直到有一个进程以只写或者读写的方式打开管道文件,才会成功运行下去。
read阻塞:
read函数也会引起阻塞运行,直到管道中有数据或者写端关闭。
write阻塞:
write函数也会引起阻塞运行,当管道文件写满的时候。
这里管道文件满,说明管道文件也有一个容量上限,我们知道管道文件在内存上,而内存的大小,在32位系统上是4GB大小,那么管道文件到底是默认多大呢(肯定比4GB小);
下面我们做一个小测试:
测试内容:
管道文件的大小:
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
void main()
{
int fd = open("FIFO",O_RDWR);
assert(fd != -1);
int count = 0;
while(1)
{
write(fd,"x",1);
count++;
printf("count == %d\n",count);
}
close(fd);
exit(0);
}
结果:
count显示是65536,而我们每次往管道文件写一个char 类型的‘x’,意思写了65536个‘x’,也就是65536个字节,当写到65536的时候,程序不再执行,说明管道文件已满,这时候,就是管道文件的最大容量。也就是管道文件默认容量 = 65536字节 = 64k字节 = 16个页面(这里的1k就是1024字节)。我们其实也可以用fcntl函数来修改管道容量(后期将解释)。
四、半双工和全双工通讯
半双工通讯:在一次通讯中,数据流向是单向的。如对讲机和以前的电报
全双工通讯:在任意时刻,数据都可以双向流通。如现在的手机通信。