进程间通讯——FIFO篇

进程间通讯——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’之前的字符个数)读取后面的内容
在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值