进程间通信(一)-----管道

来源:互联网 发布:tp wifi访客网络 编辑:程序博客网 时间:2024/05/18 19:46
进程间通信的本质:让不同的进程看到一份公共的资源(由于进程之间相互独立,有各自不同的地址空间,所以对于一个进程中的数据,在其他进程中是看不到的,虽然这在一定程度上保证了进程间的独立性,但若两个进程之间想要进行通信,则必须借助内核,在内核中开辟一块缓冲区,两个进程均能看到这块缓冲区,让一个进程将数据从进程的地址空间拷到内核缓冲区中,再由另外一个进程从内核缓冲区中读数据,放到自己的地址空间当中)
下面介绍四种进程间通信方式:
一.管道
1.匿名管道
介绍一个系统调用用于创建匿名管道:
int pipe(int filedes[2]);
在调用这个函数之后,内核会开辟出一块缓冲区用于通信,而这个缓冲区可以看做一个没有名字的管道文件,对应着进程中的两个文件描述符(这两个文件描述符保存在filedes[2]这个数组当中,filedes[0]表示读端,filedes[1]表示写端),读端表示向管道中读数据,而写端则是向管道中写数据
对于这个函数的返回值,成功返回0,否则返回-1
pipe函数提供了一份内核缓冲区,也就为进程间通信提供可能,具体如何实现进程间通信,步骤如下:

①首先由我们的进程调用pipe这个系统调用,创建一个管道,此时进程对应的文件描述符表中的两个文件描述符被占用,一个作为读端保存在filedes[0]当中,而另一个则作为写端保存在filedes[1]当中;
②调用fork(),此时创建子进程,而由于子进程会继承父进程的文件描述符表,所以在子进程的文件描述符表当中,和父进程一样会有对应的文件描述符表示管道的读写端,并且文件描述符的值也是相同的;
③由于管道不能实现双向通信,只能实现单向通信,所以调用close函数,关闭对应进程不需要的文件描述符,如上图,则是关闭了父进程的读端文件描述符,以及子进程的写端文件描述符,从而实现父进程写,子进程读的单向通信;

测试代码如下:
#include <stdio.h>#include <unistd.h>#include <string.h>int main(){    int fd[2];    if(pipe(fd)<0)    {        perror("pipe");        return -1;    }    pid_t pid=fork();    if(pid<0)    {        perror("fork");        return -1;    }    else if(pid==0)    {        char buf[256];        close(fd[1]);        while(1)        {            int rd=read(fd[0],buf,sizeof(buf));            buf[rd]=0;            printf("father say# %s\n",buf);        }    }    else    {        char* buf="hello child,I'm father";        close(fd[0]);        while(1)        {            sleep(1);            write(fd[1],buf,strlen(buf));        }    }    return 0;}
测试结果如下:

对于匿名管道而言,有以下几点特性:
①支持单向通信,虽说对于匿名管道而言,有读端,有写端,但是匿名管道仅支持单向通信,而不支持双向通信,原因在于仅有一个管道(一份内核缓冲区),若两个进程都能写的话,那么就容易造成管道中数据的混乱,所以一般会将读写端的其中一端关闭,防止误操作;
②只能用于存在血缘关系的进程间通信,原因在于匿名管道的创建依赖于fork,利用子进程继承父进程的文件描述符表这一特性;
③面向数据流通信,对于数据读写的多少没有硬性规定(对比数据块通信);
④生命周期随进程,原因在于当进程退出时,会将进程的资源进行释放,包括文件描述符表;
⑤支持同步与互斥机制;

除此之外,对于匿名管道而言,还有四种特殊情况:(前提是所有IO操作均为阻塞式IO,未设置O_NONBLOCK非阻塞标志)
①将指向管道写端的所有文件描述符都关闭,此时还不断的从管道中读数据,管道中的数据只少不多,直到所有数据都被读取完,此时read返回0,相当于到了文件末尾,此时read不会阻塞;
②不将指向管道写端的所有文件描述符关闭,此时不再向管道中写数据了,但还会不断的从管道中读数据,在这种情况下,当管道中的数据被读完之后,read会阻塞,意味着等待写端进行写数据;
③将指向管道读端的所有文件描述符都关闭,此时还想管道中写数据,那么该进程就会收到SIGPIPE信号,从而进程异常退出;
④不将指向管道读端的所有文件描述符关闭,此时不再从管道中读数据了,但还会不断的向管道中写数据,在这种情况下,当管道被写满之后,write就会阻塞,意味着等待读端进行读数据;

2.命名管道FIFO
管道除了匿名管道之外,还有命名管道,而命名管道的一大特性就是先进先出(先写入文件的数据,先被读取),其实命名管道的基本特性与匿名管道一致,命名管道利用了文件系统,真正的创建了一个管道文件,而正是因为有了这个实体文件在磁盘上,不同的进程可以通过各自的文件描述符来打开这个文件,从而达到进程间通信的目的,与匿名管道相比,在这一点上,命名管道不再只局限于存在血缘关系的进程间通信了,两个没有任何关系的进程也可以通过命名管道来进行通信,这就是命名管道与匿名管道最大的区别。

命名管道的创建:
①直接通过Shell下的命令行,以命令进行创建命名管道文件(mknod命令和mkfifo命令)
②利用系统调用接口进行创建:
int mknod(const char* path,mode_t mod,dev_t dev);
int mkfifo(const char* path,mode_t mod);
分析上面的两个函数,对于path参数和mod参数,前者表示命名管道的路径,后者表示创建的命名管道文件的文件权限(0666|S_IFIFO指明创建一个命名管道且权限为0666,这里要注意umask对文件权限的影响),;而对于mknod函数的第三个参数dev表示设备值,一般为0,取决于文件创建的种类,它只有在创建设备文件的时候才会被用到
对于返回值,两者均是成功返回0,失败返回-1
测试用例:
------------------write-----------------------------------------------------#include <stdio.h>#include <unistd.h>#include <string.h>#include <sys/stat.h>#include <sys/types.h>#include <fcntl.h>int main(){    mknod("fifo_m",0666|S_IFIFO,0); //这里也可以是mkfifo("fifo_m",0666|S_IFIFO);    int fd=open("fifo_m",O_WRONLY);    if(fd<0)    {        perror("open");        return -1;    }    char* buf="HELLO,I am fifo_write";    while(1)    {        sleep(1);        write(fd,buf,strlen(buf));    }    return 0;}

--------------------read-------------------------------------#include <stdio.h>#include <unistd.h>#include <sys/stat.h>#include <sys/types.h>#include <fcntl.h>int main(){    int fd=open("fifo_m",O_RDONLY);    if(fd<0)    {        perror("open");        return -1;    }    char buf[256];    while(1)    {        int rd=read(fd,buf,sizeof(buf));        buf[rd]=0;        printf("the fifo_write say# %s\n",buf);    }    return 0;}

测试结果如下:



原创粉丝点击