管道的一个问题就是没有名字,因此只能用于有亲缘关系的进程间通信。
有名管道(named pipe或FIFO)它提供一个路径名与之关联,以FIFO的文件形式存储于文件系统中。有名管道是一个设备文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。FIFO总是按照先进先出的原则工作。
有名管道的创建与读写
Linux下有两种方法创建有名管道。一是在Shell下交互地建立一个有名管道。Shell下可使用mknod或mkfifo命令,比如:
mknod namedpipe
或者是在程序里用函数。
#include<sys/types.h>
#include<sys/stat.h>
int mknod(const char *path,mode_t mod,dev_t dev);
int mkfifo(const char *path,mode_t mode);
参数中path为创建的有名管道的全路径名:mod为创建的有名管道的模式,指明其存取权限,和creat函数的参数是一个意思。dev为设备值,该值取决于文件创建的种类,它只在创建设备文件时才会用到。这两个函数成功调用都返回0,失败都返回-1
umask(0);
if(mknod("tmp/fifo",S_IFIFO|0666,0)==-1){perror("mknod error!");exit(1);
}
S_FIFO|0666 指明创建了一个有名管道且存取权限为0666,即创建者,与创建者同组的,其他用户对该有名管道的访问权限都是可读可写,这方面在文件读写章节有介绍
有名管道创建后就可以使用了,有名管道和管道的使用方法基本是相同的。只是使用有名管道时,必须先调用open打开。因为有名管道是存在于硬盘上的文件,而管道是存在于内存中的特殊文件。
需要注意的事,调用open打开有名管道的进程可能会被阻塞。但如果同时用读写方式O_RDWR打开,则一定不会导致阻塞;如果以只读方式O_RDONLY打开,则调用open函数的进程将会被阻塞直到有写方式打开管道;同样以只写方式O_WRONLY打开也会阻塞直到有读方式打开管道。
示例程序1
演示有名管道在无亲缘关系的进程如何进行通信。示例包含两个程序
//procread程序
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#define FIFO_NAME "myfifo"
#define BUF_SIZE 1024
int main(int argc,char **argv,char **environ){int fd;char buf[BUF_SIZE];umask(0);fd=open(FIFO_NAME,O_RDONLY);read(fd,buf,BUF_SIZE);close(fd);exit(0);
}
这个代码就是很简单的文件读取,没什么好说的。
然后是写的程序,在这里创建了有名管道
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#define FIFO_NAME "myfifo"
#define BUF_SIZE 1024
int main(int argc,char **argv,char **environ){int fd;char buf[BUF_SIZE]="Hello procwrite,I come from process named procread!";umask(0);if(mkfifo(FIFO_NAME,S_IFIFO|0666)==-1){perror("mkfifo error!");exit(1);}if((fd=open(FIFO_NAME,O_WRONLY))==-1){perror("fopen error!");exit(0);}write(fd,buf,strlen(buf)+1);close(fd);exit(0);
}
先运行write进程,运行后处于阻塞状态,因为要等另一个进程以写方式打开有名管道,然后再另一个终端运行read,结果如下:
同时也有一个myfifo的文件。
有名管道的应用实例
这回也同样创建两个FIFO来实现不同进程的全双工通信。下面的程序演示两个进程间的聊天程序。一个为server端,一个为client端。
server端:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<errno.h>
#include<fcntl.h>
#define FIFO_READ "readfifo"
#define FIFO_WRITE "writefifo"
#define BUF_SIZE 1024
int main(int argc,char **argv,char **environ){int wfd,rfd;//读写的管道char buf[BUF_SIZE];int len;umask(0);if(mkfifo(FIFO_WRITE,S_IFIFO|0666)){//服务器程序创建写管道printf("can't create fifo %s because %s",FIFO_WRITE,strerror(errno));exit(1);}umask(0);wfd=open(FIFO_WRITE,O_WRONLY);//服务器程序以写方式打开写管道if(wfd==-1){printf("open fifo %s error:%s",FIFO_WRITE,strerror(errno));exit(1);}while((rfd=open(FIFO_READ,O_RDONLY))==-1){//这是一个循环,当客户端程序创建读管道时才继续下去sleep(1);}while (1){printf("Server:");fgets(buf,BUF_SIZE,stdin);//从标准输入(也就是终端)读入字符串buf[strlen(buf)-1]='\0';//从标准输入读的字符串最后会有\n,把它改成\0if(strncmp(buf,"quit",4)==0){//如果是quit,就结束进程close(wfd);unlink(FIFO_WRITE);//unlink在文件删除这个文章介绍过close(rfd);exit(0);}write(wfd,buf,strlen(buf));//写len=read(rfd,buf,BUFSIZ);//读if(len>0){buf[len]='\0';printf("Client:%s\n",buf);}}}
client端:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<errno.h>
#include<fcntl.h>
//注意这里的fifo_write和fifo_read和服务器程序不一样,其他就几乎没啥区别了
#define FIFO_WRITE "readfifo"
#define FIFO_READ "writefifo"
#define BUF_SIZE 1024
int main(int argc,char **argv,char **environ){int wfd,rfd;char buf[BUF_SIZE];int len;umask(0);if(mkfifo(FIFO_WRITE,S_IFIFO|0666)){printf("can't create fifo %s because %s",FIFO_WRITE,strerror(errno));exit(1);}while((rfd=open(FIFO_READ,O_RDONLY))==-1){sleep(1);}umask(0);wfd=open(FIFO_WRITE,O_WRONLY);if(wfd==-1){printf("open fifo %s error:%s",FIFO_WRITE,strerror(errno));exit(1);}while (1){len=read(rfd,buf,BUFSIZ-1);if(len>0){buf[len]='\0';printf("Client:%s\n",buf);}printf("Client:");fgets(buf,BUF_SIZE,stdin);buf[strlen(buf)-1]='\0';if(strncmp(buf,"quit",4)==0){close(wfd);unlink(FIFO_WRITE);close(rfd);exit(0);}write(wfd,buf,strlen(buf));}}
分两个终端去运行