进程间通信 管道和FIFO
管道,顾名思义就是像水管一样,水管是用来流通水的,而对与管道来说,做为进程间通信的一种方式,传送的是数据而已。对于管道来说,没有名字,在程序内部以标识符进行识别,这也就注定了管道只能用于有亲缘关系的各个进程间使用。但是这一缺点可以通过FIFO,又程为有名管道来弥补,先介绍一下管道的基本函数。
#include<unistd.h>
intpipe(int fd[2]);
pipe函数用于管道的创建,函数返回两个文件描述符,fd[0]用来打开读和fd[1]用来打开写,管道是单个进程创建的,但是很少在单个进程之间使用,大部分的使用例子如下,父进程创建了一个管道,通过fork调用创建了子进程,子进程拥有父进程打开管道的描述符,然后双方一方用于从管道中读出数据,另一方用于从管道中写入数据,以此来进行进程之间的通信。
下面写一个程序,完成如下功能,客户服务程序从标准输入读取文件路径名,然后通过管道传送给服务器,然后服务器调用系统函数读取文件内容,然后通过管道返回给客户端,客户端收到内容以后输出到标准输出或者标准错误上去,由于双方都需要读写,所以需要使用双管道进行通信
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<fcntl.h> #include<sys/types.h> int main(int argc,char *argv[]) { int fone[2],ftwo[2];//双管道,描述符 void server(int,int);//服务器程序 void client(int,int);//客户端程序 pid_t childfd; pipe(fone); pipe(ftwo);//创建管道 if((childfd=fork())<0) perror("fail to fork!"); if(childfd==0){ close(fone[1]); close(ftwo[0]); server(fone[0],ftwo[1]);//调用服务器程序 exit(0); } close(fone[0]); close(ftwo[1]); client(fone[1],ftwo[0]);//调用客户端程序 waitpid(childfd,NULL,0); return 0; } void server(int readfd,int writefd) { char buf[1024]; int fd,n,len; if((n=read(readfd,buf,1024))==0)//读出文件路径 { sprintf(buf,"can not recive the massage %s\n",strerror(errno)); n=strlen(buf); write(writefd,buf,n); } if((fd=open(buf,O_RDONLY))<0)//打开文件 { sprintf(buf+n,"sorry can not open :%s",strerror(errno)); n=strlen(buf); write(writefd,buf,n); } else { printf("server:runing\n"); while((n=read(fd,buf,1024))>0)//读文件 write(writefd,buf,n);//写入管道 close(fd); } } void client(int writefd,int readfd) { char buf[1025]; int len,n; fgets(buf,1024,stdin);//从标准输入读取路径 printf("filename is :%s",buf); len=strlen(buf); if(buf[len-1]=='\n') buf[len-1]='\0'; write(writefd,buf,len);//写入管道 printf("client runing!\n"); while((n=read(readfd,buf,1024))>0)//从管道读出数据 write(1,buf,n);//输出到标准输出 }
标准I/O函数库提供了另外函数对管道进行操作 #include<stdio.h> FILE *popen(const char *command,const char *type); int pclose(FILE *stream); popen创建了一个管道,并启动另外一个进程,command是shell命令,如果type为r,那么调用进程读command的标准输出,如果type是w,调用进程往command写。这个函数使用起来还是比较方便的,个人觉得比exec函数更加容易控制,不过对于popen函数而言,只能重定向标准输入和标准输入,如果是标准出错的话,还是会输出到程序本书的标准出错中去的。 管道没有名字,因此只能用于有亲缘关系的进程之间使用,FIFO类似与管道,先进先出,单项数据流,与管道不同的就是能够实现无亲缘关系之间通信。 #include<sys/types.h> #include<sys/stat.h> int mkfifo(const char *pathname,mode_t mode); mkfifo用于管道的创建 pathname是unix路径名,mode用于指定权限位,定义在sys/stat.h中,隐含指定了O_CREAT|O_EXCL如果没有的话创建,如过存在的话,返回错误。 我们可以用管道重新写上面的客户服务器模式,只需要把创建两个管道,换成创建两个fifo就行了,fifo以pathname为参数名创建以后打开进行通信。 关于管道和FIFO阻塞的问题。如果一个函数读取一个阻塞的管道或者FIFO的话,如果该管道里面没有数据,那么读数据挂起,一直到管道内部有数据为止,如果设置成非阻塞的话,那么没有数据的话读取返回一个错误。FIFO的阻塞标志可以在open的时候指定。 open(fifo,O_WRONLY|O_NONBLOCK,0); O_NONBLOCK非阻塞标志设置,可以进行与或;对于管道来说,没有名字也就没有办法打开,可以用fcntl来获取和设置 flag=fcntl(fd,F_GETFL,0); 用来获取标志位 与或以后用 fcntl(fd,F_SETFL,flag);进行设置。 下面就写一个无亲源关系的客户服务器程序,服务器以公共的名字创建FIFO,客户以一定的规则加上自己的pid创建自己的FIFO。客户向服务器发送自己pid和文件名,服务器返回文件的内容到客户的有名管道中,供客户读出。 服务器端程序:#include<stdio.h> #include<stdlib.h> #include<sys/stat.h> #include<sys/types.h> #include<fcntl.h> #include<errno.h> #define flag O_CREAT|O_EXCL #define mode S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP int main() { int fdread,fdwrite,fdfile; long len,pid,usrfd; char buff[1024],usrfile[1024],filename[1024]; if(mkfifo("/tmp/server.fifo",mode)<0){ perror("server:fail to mkfifo server.fifo"); exit(1); } fdread=open("/tmp/server.fifo",O_RDONLY); fdwrite=open("/tmp/server.fifo",O_WRONLY); read(fdread,buff,1024); sscanf(buff,"%ld %s",&pid,filename); sprintf(usrfile,"/tmp/fifo.client.%ld",(long)pid); if((usrfd=open(usrfile,O_WRONLY))<0){ perror("server:fail to open the client"); exit(1); } if((fdfile=open(filename,O_RDONLY))<0){ perror("server:fail to open the file "); exit(1); } while((len=read(fdfile,buff,1024))>0) write(usrfd,buff,len); close(usrfd); close(fdfile); return 0; }
客户端程序:
#include<stdio.h> #include<stdlib.h> #include<sys/stat.h> #include<sys/types.h> #include<fcntl.h> #include<errno.h> #include<string.h> #define flag O_CREAT|O_EXCL #define mode S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP int main() { pid_t pid; char buf[1024],filename[1024]; char clientfifo[1024]; int fdserver,fdclient; int len; pid=getpid(); scanf("%s",filename); sprintf(buf,"%ld %s",(long)pid,filename); if((fdserver=open("/tmp/server.fifo",O_WRONLY))<0){ perror("client:fail to open the server.fifo"); exit(1); } len=strlen(buf); write(fdserver,buf,len); sprintf(buf,"/tmp/fifo.client.%ld",(long)pid); if(mkfifo(buf,mode)<0){ perror("client:fail to mkfifo the"); exit(1); } if((fdclient=open(buf,O_RDONLY))<0){ perror("client:fail to open client fifo"); exit(1); } while((len=read(fdclient,buf,1024))>0) write(1,buf,len); close(fdserver); close(fdclient); return 0; }
可以实现单服务器,多用户模式,服务器在程序后台运行,前台向服务器提交请求,服务器端进行回应。系统对于管道和FIFO有如下限制,OPEN_MAX一个进程在任意时刻可以打开的最大描述符个数,PIPR_BUF可原子的往一个管道或者fifo的最大数据量关于这两个限制,可以通过sysconf函数查看,用过ulimit命令和limit命令进行修改。其中很多规则制约这fifo和管道的是否阻塞问题和原子操作问题,关于这方面的讨论,才疏学浅,不便多说了。