进程间通讯——FIFO有名管道
管道没有名字,只局限于有共同祖先进程的各个进程之间。FIFO不同于管道的是有一个路径名与之相关联,允许无亲缘关系的进程访问同一个FIFO。由于FIFO是半双工的,因此FIFO不能打开来既读又写。
注:
1、创建并打开一个管道只需要调用pipe;创建并打开一个FIFO则需在调用mkfifo后再用open;
2、管道在所有进程最终关闭它之后自动消失,FIFO的名字只能通过调用unlink才从文件系统中删除。
FIFO由mkfifo函数创建:
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);
//其中,pathname表示路径名;mode表示写入/读出的模式
//返回值,成功则为0,出错则为-1;
有缘进程之间通信
使用两个FIFO的客户-服务器模型:
使用两个FIFO的客户-服务器main()函数:
#include "unpipc.h"
#include <sys/types.h>
#include <sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
void client(int,int),server(int,int);
void client(int readfd,int writefd) //readfd、writefd就是索引值
{
size_t len;
ssize_t n;
char buff[MAXLINE];
fgets(buff,MAXLINE,stdin); //fgets()函数:从标准输入stdin中读取MAINLINE个字节存入buff中,并未限制必须读取MINLINE个字节才能返回,主要读到数据就马上返回
len=strlen(buff); //strlen()求字符串的实际长度,计算'\0'之前的字符数
if(buff[len-1]=='\n') //从标准输入读进路径后,删除由fgets存入的换行符,再写入管道
len--;
Write(writefd,buff,len); //将参数写至IPC通道中
while((n=Read(readfd,buff,MAXLINE))>0)
{
write(STDOUT_FILENO,buff,strlen(buff));
} //将buff中的数据写道标准输出
}
//从readfd中打开输入的文件名,并将文件名对应的文件内容写入文件描述符writefd
void server(int readfd,int writefd)
{
int fd;
ssize_t n;
char buff[MAXLINE+1];
/*从IPC管道中读取数据*/
if((n=read(readfd,buff,MAXLINE))==0)
err_quit("出错信息");
buff[n]='\0';
if((fd=open(buff,O_RDONLY))<0){
/*错误必须告知客户端*/
snprintf(buff+n,sizeof(buff)-n,":can't open,%s\n",strerror(errno));
n=strlen(buff);
write(writefd,buff,n);
}else{
/*打开成功:将文件复制至IPC通道*/
while(n=Read(fd,buff,MAXLINE)>0)
{
write(writefd,buff,strlen(buff));
}
Close(fd);
}
}
int main(int argc, char **argv)
{
int readfd,writefd;
pid_t childpid;
/*创建两个有名管道*/
if((mkfifo(FIFO1,S_IFIFO | 0666)<0)&&(errno!=EEXIST))
err_quit("can't creat %s",FIFO1);
if((mkfifo(FIFO2,S_IFIFO | 0666)<0)&&(errno!=EEXIST)){
unlink(FIFO1); //管道在左右进程最终关闭才消失,调用unlink才能从文件系统中删除
err_quit("can't creat %s",FIFO2);
}
if(childpid=Fork()==0){ //子进程
readfd=Open(FIFO1,O_RDONLY,0);
writefd=Open(FIFO2,O_WRONLY,0);
server(readfd,writefd);
exit(0);
}
/*父进程*/
writefd=Open(FIFO1,O_WRONLY,0); //子进程先读后写,父进程就要先写后读:避免父子进程都打开同一个FIFO进行读,造成阻塞,这种现象成为死锁。
readfd=Open(FIFO2,O_RDONLY,0);
client(readfd,writefd);
waitpid(childpid,NULL,0); //等待子进程终止
Close(readfd);
Close(writefd);
Unlink(FIFO1); //调用unlink将FIFO1、FIFO2从文件系统中删除
Unlink(FIFO2);
exit(0);
}
运行结果,如下图:
FIFO管道方式相较于pipe管道方式的区别在于:
1.创建并打开一个管道只需调用pipe()函数。创建并打开一个FIFO则需要调用mkfifo后再调用open;
2.管道再所有进程都关闭后自动消失,FIFO的名字则只能通过调用unlink才能从文件系统中删除
3.FIFO需要额外调用点好处就是FIFO在文件中有一个名字,该名字允许某个进程创建一个FIFO,与它无亲缘关系的另一个进程来打开这个FIFO。
以上的main()函数内的内容仍然是表示的是有亲缘关系的进程,无亲缘关系的进程如下内容所述。
无亲缘关系的客户与服务器
server_main.c(独立的服务器程序main函数):
#include "unpipc.h"
#include <sys/types.h>
#include <sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
void server(int,int);
void server(int readfd,int writefd)
{
int fd;
ssize_t n;
char buff[MAXLINE+1];
/*从IPC管道中读取数据*/
if((n=read(readfd,buff,MAXLINE))==0)
err_quit("出错信息");
buff[n]='\0';
if((fd=open(buff,O_RDONLY))<0){
/*错误必须告知客户端*/
snprintf(buff+n,sizeof(buff)-n,":can't open,%s\n",strerror(errno));
n=strlen(buff);
write(writefd,buff,n);
}else{
/*打开成功:将文件复制至IPC通道*/
while(n=Read(fd,buff,MAXLINE)>0)
{
write(writefd,buff,strlen(buff));
}
Close(fd);
}
}
int main(int argc, char* argv[])
{
int readfd,writefd;
/*创建两个有名管道*/
if((mkfifo(FIFO1,S_IFIFO | 0666)<0)&&(errno!=EEXIST))
err_quit("can't creat %s",FIFO1);
if((mkfifo(FIFO2,S_IFIFO | 0666)<0)&&(errno!=EEXIST)){
unlink(FIFO1); //管道在左右进程最终关闭才消失,调用unlink才能从文件系统中删除
err_quit("can't creat %s",FIFO2);
}
readfd=Open(FIFO1,O_RDONLY,0);
writefd=Open(FIFO2,O_WRONLY,0);
server(readfd,writefd);
exit(0);
}
client_main.c(独立客户程序main函数)
#include "unpipc.h"
#include <sys/types.h>
#include <sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
void client(int,int);
void client(int readfd,int writefd) //readfd、writefd就是索引值
{
size_t len;
ssize_t n;
char buff[MAXLINE];
fgets(buff,MAXLINE,stdin); //fgets()函数:从标准输入stdin中读取MAINLINE个字节存入buff中,并未限制必须读取MINLINE个字节才能返回,主要读到数据就马上返回
len=strlen(buff); //strlen()求字符串的实际长度,计算'\0'之前的字符数
if(buff[len-1]=='\n') //从标准输入读进路径后,删除由fgets存入的换行符,再写入管道
len--;
Write(writefd,buff,len); //将参数写至IPC通道中
while((n=Read(readfd,buff,MAXLINE))>0)
{
write(STDOUT_FILENO,buff,strlen(buff));
} //将buff中的数据写道标准输出
}
int main(int argc, char* argv[])
{
int readfd,writefd;
writefd=Open(FIFO1,O_WRONLY,0); //子进程先读后写,父进程就要先写后读:避免父子进程都打开同一个FIFO进行读,造成阻塞,这种现象成为死锁。
readfd=Open(FIFO2,O_RDONLY,0);
client(readfd,writefd);
Close(readfd);
Close(writefd);
Unlink(FIFO1); //调用unlink将FIFO1、FIFO2从文件系统中删除
Unlink(FIFO2);
exit(0);
}
运行两个无缘进程,结果入下图所示:
open()函数原型:
int open(const char *pathname,int flags);
int open(const char *pathname, int flags,mode_t mode);
返回值:=-1:证明打开文件失败 成功:返回一个小的非负整数 ,表示open打开的文件描述符,该文件描述符后的操作(写入、读出等操作)起到索引的作用。
open()函数三个参数说明:pathname、flags、mode
pathname:表示想要打开的文件名:/tmp/fifo
flags:打开文件的方式及权限,它一般有三个值:O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(可读可写)
其余四值:
O_CREAT:若文件不存在,用这个值可以创建文件;若文件存在,用其会出错;
O_EXCL:如果同时指定了O_CREAT,使用其项时,需要说明mode;
O_APPEDN:每次写入时,都加到文件的末端;
O_TRUAC:属性去打开文件时,如果这个文件本来就有内容的,会被缩减长度为0
mode:表示创建出的新文件的操作权限。
单个服务器,多个客户实现
FIFO的优势在于服务器可以是一个长期运行的进程。服务器端以已知的路径名创建一个FIFO,并打开该FIFO来读,读入客户的请求。每个客户在启动时创建自己的FIFO(含有自己的进程ID),每个客户把自己的请求及信息(路径名的文件及进程ID)发送给服务器创建的管道FIFO,该路径名的文件即为服务器需要打开并发回客户端的的文件。
其示意图具体如下:
server 源码:
/*
* server.c
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
#include<stdlib.h>
#include<fcntl.h>
#include<limits.h>
#include<string.h>
#define FIFO_SERVER "fifo.server"//all known
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
#define SIZE PIPE_BUF
int main(){
if(mkfifo(FIFO_SERVER,FILE_MODE)<0 && errno!=EEXIST){
printf("mk error \n");
exit(1);
}
int readfd=open(FIFO_SERVER,O_RDONLY);
int writefd=open(FIFO_SERVER,O_WRONLY);
if(readfd==-1||writefd==-1){
printf("open error \n");
exit(1);
}
int r;char buf[SIZE];char *pathname;pid_t pid;char client_fifo_name[SIZE];
while((r=read(readfd,buf,SIZE))>0){
buf[r]='\0';
if((pathname=strchr(buf,' '))==NULL){
printf("not include pid\n");
continue;
}
// *pathname++='\0';//*pid_s=0 pid_s++
*pathname='\0'; //将字符串' '换成'\0'——'\0'字符串结束符
pathname++; //指向下一地址
pid=atol(buf);
printf("buf: %s\n",buf);
printf("pathname: %s\n",pathname);
snprintf(client_fifo_name,sizeof(client_fifo_name),"fifo.%ld",(long)pid);
if((writefd=open(client_fifo_name,O_WRONLY))<0){
printf("error\n");
continue;
}
int fd;
if((fd=open(pathname,O_RDONLY))<0){
printf("open this file error\n");
write(writefd,"open this file error\n",sizeof("open this file error\n"));
close(writefd);
}else {
while((r=read(fd,buf,SIZE))>0){
write(writefd,buf,r);
}
close(fd);
}
close(writefd);
unlink(FIFO_SERVER);
}
return 0;
}
client 源码
/*
* client.c
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<limits.h>
#include<fcntl.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#define FIFO_SERVER "fifo.server"
#define SIZE PIPE_BUF
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
int main(){
pid_t pid=getpid();
char buf[SIZE];
char client_fifo_name[SIZE];//from server
snprintf(client_fifo_name,sizeof(client_fifo_name),"fifo.%ld",(long)pid);
if(mkfifo(client_fifo_name,FILE_MODE)<0 && errno!=EEXIST){
printf("mk error \n");
exit(1);
}
//双引号"%ld "内的空格非常重要
snprintf(buf,sizeof(buf),"%ld ",(long)pid);
int len=strlen(buf); //取‘\0’之前的字符数,此处包括' '占的一位
char *pathname=buf+len; //将指针移到未被填充字节的位置
fgets(pathname,SIZE-len,stdin); //此时buf内的数据包括:pid+空字符+stdin数据+'\n'+'\0'
len =strlen(buf);//
printf("buf: %s",buf);
int writefd=open(FIFO_SERVER,O_WRONLY);
write(writefd,buf,len-1);//len-1==not include \n
int readfd=open(client_fifo_name,O_RDONLY);
int r;
while((r=read(readfd,buf,SIZE))>0){
write(STDOUT_FILENO,buf,r);
}
close(readfd);
unlink(client_fifo_name);//delete this temp file
return 0;
}
运行结果附图:
以上server及client代码释义:服务器在文件系统中创建server.fifo文件,根据客户端的ID号创建属于本客户端使用的文件。客户端通过server.fifo向服务器发送【客户端的ID+空格+文件名】,服务器创建FIFO文件,读取文件内的数据并发送至server.fifo中,客户端通过fifo.pid文件读取数据,并显示到
STDOUT。
以下例子说明,文件结束符在字符串中的作用,需要明确的是,虽然在字符串中,文件结束符’\0’后面还有数据内容,在读出和处理时,到达结束符’\0’就结束,若要获取结束符’\0’后面的数据,需要重新指定ptr=test(指向字符串首元素地址)+len(strlen(test)取结束符’\0’之前的字符个数)读取后面的内容。