想必大家一定听说过这样一句话:Linux中一切皆文件。而管道就是进程间基于内存文件的通信机制。也是最传统的通信机制。管道,想象一下我们现实生活中的管道,都是有两端,两端都可以进,也都可以出。但是,我们都知道一般安装好了的管道都是一边用于进,一边用于出,毕竟使用管道就是用来帮我们传输一些流体的。相应的,在Linux中,我们也可以使用管道来传输我们所需要的数据流。
匿名管道
当我们在使用fork函数创建子进程时,子进程是会“继承”父进程几乎所有的资源(在这里就不细细讨论了),当然也包括父进程打开的文件,使用open函数打开一个文件,返回的是这个文件的文件描述符。理所当然的,在子进程中也是可以看到这个文件的文件描述符的,既然拿到了一个文件的文件描述符,并且拥有与父进程完全一样的权限来对此文件进行操作(父进程以读写方式打开该文件,子进程也可以对该文件进行读写;父进程以只读的方式打开,子进程也只能读该文件,而不能写)。
如果我们在子父进程中选择一个用来写数据,另一个用来读数据,这不就实现了子父进程之间的通信了么。来看一看具体实现过程,首先,使用读写的方式打开一个文件,然后使用fork创建一个子进程,然后关闭父进程对该文件的写操作权限和子进程对该文件的读操作权限。在这里解释一下,既然刚打开就要分别关闭子父进程的一个权限,为什么还一定要以读写的方式打开。虽然,父进程只需要读该文件,但是以只读的方式打开该文件,那么,子进程对这个文件也只有只读的权限,而子进程需要有对该文件进行写操作,相反的,以只写的方式打开的话,父进程就没法读了。所以必须要以读写的方式打开。
创建匿名管道的思想就讲到这里,下边简单介绍一下用于创建匿名管道的pipe函数。
使用该函数需#include <unistd.h>,函数原型为:int pipe(int fd[2]);
参数说明:fd是一个用于存放文件描述符的数组,其中fd[0] 表示读端,fd[1]表示写端。
返回值:成功返回0,失败返回错误码
代码实现
#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <error.h> #define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) int main() { int pipefds[2]; if(-1 == pipe(pipefds)) ERR_EXIT("create pipe"); pid_t pid; pid = fork(); if(-1 == pid) ERR_EXIT("fork"); if(0 == pid)//child { close(pipefds[0]); int count = 5; char* str = "This is my first pipe!"; while(count--) { write(pipefds[1],str,strlen(str)); sleep(2); } close(pipefds[1]); exit(EXIT_SUCCESS); } else { close(pipefds[1]); char sub[100] = {0}; while(1) { int i = read(pipefds[0],sub,100); if(i < 0) ERR_EXIT("read"); else if(0 == i) { printf("Maybe Child has quit~\n"); break; } else printf("Child saied:%s\n",sub); } close(pipefds[0]); } return 0; }
该程序在父子进程之间基于一个文件创建了一个匿名管道,然后,子进程往管道里边写数据,总共写5次,隔两秒写一次;父进程从管道里边读数据,一直不停地读。那么,按理说,只要子进程往管道里边写数据了,父进程就会将其读出,则父进程读出的数据就有可能是不完整的,然而运行程序看现象,并不是这样。这是因为管道自带同步互斥机制,子进程在写数据的时候,父进程并不会读数据,而是当子进程在等待的时候,父进程才读数据,这就保证了父进程读出的数据就是原本的数据,而不会读到脏数据。
运行结果
总结一下匿名管道的特点
1、只能用于具有亲缘关系的的进程之间进行通信,比如分别是父子进程或者是同一个父进程的子进程等;
2、管道通信的时候是基于字节流的;
3、进程退出,打开的文件会随着关闭,管道也就释放了,即:管道的生命周期随进程;
4、管道自带同步互斥机制的;
5、管道是半双工(数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输),数据只能向一个方向传输,想要实现双向通信,需要创建两个管道。
命名管道
进程间通信的实质其实就是让两个不相关的进程看到同一份由操作系统提供的公共的资源。匿名管道虽然让两个进程看到了同一份公共的资源,但是,对这两个进程却是有要求的,必须是有亲缘关系的进程。那么,要使两个不相关的进程间的通信,可以通过命名管道得方式来实现。
命名管道是一种特殊类型的文件
我们可以通过使用命令 mkfifo filename 来创建一个命名管道
除了使用命令的方式,还可以使用函数的方式创建,下边主要讲一下如何使用函数来创建一个命名管道。
在此之前,先介绍一个函数
原型:int mkfifo ( const char* filename , mode_t mode);
参数:第一个参数是将要创建的管道的名字,第二个参数是创建该管道文件的权限,一般为0644(即创建者拥有读写的权利,所属组和其他拥有只读的权利就OK了)。
返回值:成功返回该管道文件的文件描述符,失败返回 -1。
在这里应用命名管道实现server 和 client 之间的通信
server.c部分
#include <stdio.h> #include <unistd.h> #include <sys/stat.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #define ERR_EXIT(m)\ do\ {\ error(m);\ exit(EXIT_FAILURE);\ }while(0) int main() { umask(0); if(mkfifo("mypipe",0644)<0) ERR_EXIT("mkfifo"); int rfd = open("mypipe",O_RDONLY); if(rfd<0) ERR_EXIT("open_r"); char buf[1024] = {0}; while(1) { buf[0] = 0; int s = read(rfd,buf,sizeof(buf)-1); if(s<0) ERR_EXIT("read"); else if(0 == s) { printf("client quik~\n"); break; } else printf("client said:%s\n",buf); } close(rfd); return 0; }
server.c 部分负责创建一个命名管道,然后就等着 client.c 往管道里边写入数据,然后再将这些这些数据进行读出。
client.c部分
#include <stdio.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #define ERR_EXIT(m)\ do\ {\ error(m);\ exit(EXIT_FAILURE);\ }while(0) int main() { int wfd = open("mypipe",O_WRONLY); if(wfd<0) ERR_EXIT("open_w"); char buf[1024] = {0}; while(1) { int s = read(0,buf,sizeof(buf)-1); if(s > 0) { buf[s] = 0; write(wfd,buf,strlen(buf)); memset(buf,0x00,sizeof(buf)); } else ERR_EXIT("write"); } close(wfd); return 0; }
client.c 部分负责往管道里边写入数据
因为这里是两个可执行的程序,所以,需要我们需要一个Makefile 文件来帮忙管理
Makefile 文件
.PHONY:all all:client server client:client.c gcc -o $@ $^ server:server.c gcc -o $@ $^ .PHONY:clean clean: rm -f client server
关于Makefile文件的讲解http://blog.csdn.net/guaiguaihenguai/article/details/78701767
关于管道的问题就讲到这里,欢迎大家提出宝贵意见。