Linux-C-day-2-进程通过--管道通信

管道通信

进程间管道通信方式可以通过man 7 pipe来查看;

匿名管道
单工管道

打开管道:使用popen(),关闭管道使用pclose(),打开文件使用fopen(),关闭文件使用pclose();打开管道使用的函数FILE *popen(const char *command,const char *open_mode);command:表示命令行字符串;open_mode:表示文件的权限,r:表示只读;w:表示只写;返回值,成功应该返回文件描述符,失败了返回NULL;
 管道的操作:
  读管道:size_t fread(void *buffer,size_t size,size_t count,FILE stream);buffer表示用于接收数据的内存地址;size:表示读取每个数据项的字节数;count:表示数据项个数;stream:表示输入流;返回值:当返回值>count时,函数出错;当返回值是整数时,表示真正读取的数据项的个数;
  写管道:size_t fwrite(const void
buffer,size_t size, size_t count,FILE *stream);buffer:表示的是写入数据的内存地址;size:表示的是写入数据项的字节数;count:表示的是写入数据项的个数;stream:表示的是写入目标文件指针;当返回值大于count时,表示出错;如果返回值是正数,表示实际写入数据项的个数;
 关闭管道:int pclose(FILE *stream);stream表示的是文件描述符,返回值-1表示失败;返回值0表示成功;
 管道的本质是启用shell和命令两个进程,从命令进程中读/写文件流,管道通信解决的是exec和system无法返回输出数据问题;
 单工管道的特点:方便实用系统自带的功能,并且可以执行比较复杂的shell,默认启动两个基础进程,但是执行效率会比较低;
pipe.c:

#include 
#include 
#include 

int main(){
    int fd[2];
    pipe(fd);
    char in[] = "Hello pipe";
    write(fd[1],in,sizeof(in));
    printf("write:%s\n",in);

    char out[sizeof(in)]={0};
    ssize_t n = read(fd[0],out,sizeof(out));
    if(-1 == n){
        perror("read error");
        return -1;
    }
    printf("read:%s\n",out);

    close(fd[0]);
    close(fd[1]);
}

pipe01.c:

#include 
#include 
#include 

int main(){
    int fd[2];
    pipe(fd);
    if(!fork()){// child
        char in[] = "Hello pipe";
        write(fd[1],in,sizeof(in));
        printf("child %d write:%s\n",getpid(),in);
    }else{// parent
        char out[BUFSIZ]={0};
        ssize_t n = read(fd[0],out,sizeof(out));
        if(-1 == n){
            perror("read error");
            return -1;
        }
        printf("parent %d read:%s\n",getpid(),out);
    }
    close(fd[0]);
    close(fd[1]);
}

pipe02.c:

#include 
#include 
#include 

int main(){
    int fd[2];
    pipe(fd);
    if(!fork()){// child
        char in[] = "Hello pipe";
        sleep(3);
        write(fd[1],in,sizeof(in));
        printf("child %d write:%s\n",getpid(),in);
    }else{// parent
        char out[BUFSIZ]={0};
        ssize_t n = read(fd[0],out,sizeof(out));
        if(-1 == n){
            perror("read error");
            return -1;
        }
        printf("parent %d read:%s\n",getpid(),out);
    }
    close(fd[0]);
    close(fd[1]);
}

pipe03.c:

#include 
#include 
#include 
#include 

int main(){
    int fd[2];
    pipe(fd);
    if(!fork()){// child
        char in[] = "Hello pipe";
        sleep(3);
        write(fd[1],in,sizeof(in));
        printf("child %d write:%s\n",getpid(),in);
    }else{// parent
        fcntl(fd[0],F_SETFL,O_NONBLOCK);
        char out[BUFSIZ]={0};
        ssize_t n = read(fd[0],out,sizeof(out));
        if(-1 == n){
            perror("read error");
            return -1;
        }
        printf("parent %d read:%s\n",getpid(),out);
    }
    close(fd[0]);
    close(fd[1]);
}

pipe04.c:

#include 
#include 
#include 
#include 

int main(){
    int fd[2];
    pipe(fd);
    if(!fork()){// child
        close(fd[0]);
        char in[] = "Hello pipe";
        sleep(3);
        write(fd[1],in,sizeof(in));
        printf("child %d write:%s\n",getpid(),in);
        close(fd[1]);
    }else{// parent
        close(fd[1]);
        fcntl(fd[0],F_SETFL,O_NONBLOCK);
        char out[BUFSIZ]={0};
        ssize_t n = read(fd[0],out,sizeof(out));
        if(-1 == n){
            perror("read error");
            return -1;
        }
        printf("parent %d read:%s\n",getpid(),out);
        close(fd[0]);
    }
}

read_cmd.c:

#include 
#include 
#include 

int main(){
    FILE* fd = popen("ps -ef","r");
    //FILE* fd = popen("ls -l","r");
        
    char buf[BUFSIZ];
    size_t count = 0;
    printf("read data:\n");
    do{
        memset(buf,'\0',BUFSIZ);
        size_t n = fread(buf,sizeof(char),BUFSIZ-1,fd);
        if( n > BUFSIZ - 1 ){
            perror("fread error");
            exit(EXIT_FAILURE);
        }
        count += n;
        printf("%s",n,buf);
    }while(!feof(fd));
    printf("total size:%ld\n",count);
    pclose(fd);
}

write_cmd.c:

#include 
#include 
#include 

int main(){
    FILE* fd = popen("wc","w");
    //FILE* fd = popen("ls -l","r");
    //char str[] = "123 456";       
    char str[] = "123 456\n";       
    size_t n = fwrite(str,sizeof(char),sizeof(str),fd);
    if(n > sizeof(str)){
        fprintf(stderr,"FILE:%d,LINE:%d-fwrite error",__FILE__,__LINE__);
        exit(EXIT_FAILURE);
    }
    pclose(fd);
}
半双工管道

创建管道:int pipe(int filedes[2]),定义两个管道,0读1写;也就是使用filedes[0]表示读管道;filedes[1]表示写管道;返回值-1表示失败,返回值0表示成功;
 读写管道:ssize_t write(int fd,const void *buf,size_t nbyte);其中fd表示问价描述符;buf:写入数据的内存单元;nbyte:写入文件指定的字节数;返回值-1表示管道写入失败;如果返回值为正数,表示写入的字节数;ssize_t read(int fd, void *buf,size_t count),其中fd表示文件描述符;buf表示写入数据的内存单元;返回值-2表示出错,0表示管道中没有数据,正数表示读取的字节数;
 管道的控制:如果管道是空的,read()函数默认进入阻塞状态;函数fcntl(int fd, int cmd,long arg );fd表示文件描述符,cmd:F_GETFL表示获取文件描述符;F_SETFL表示设置文件描述符;arg:O_NONBLOCK表示非阻塞,O_BLOCK表示阻塞;fcntl(filedes,F_SETFL,O_NONBLOCK)表示将文件描述符改为非阻塞的;
 半双工管道的本质:POSIX标准:文件描述符,整型:int 整数,常用的函数包括open、close、read、write、定位偏移lseek;ANSI C标准:常用的函数,fopen、fclose、fread、fwrite、fseek;文件流是文件系统之上的封装,文件流通过增加缓冲区域减少读写系统的调用次数来提高读写效率,在晋城的用户空间封装成FILE 结构,以提高可移植性和效率;
 两个案例:
ps_self01.c:用于打印自身的进程信息,但是未打印子进程信息

#include 
#include 

int main(){
    char cmd[128]={0};
    sprintf(cmd,"ps -p %d -o pid,ppid,stat,cmd",getpid());
    system(cmd);
}

ps_self02.c:用于打印出自身和ps进程的信息,但是未打印出grep子进程信息

#include 
#include 
#include 

int main(){
    FILE* readfd = popen("ps -o pid,ppid,stat,cmd","r");
    char buf[BUFSIZ]={0};
    fread(buf,sizeof(char),sizeof(buf),readfd);
    fclose(readfd);
    char cmd[128]={0};
    sprintf(cmd,"grep %d",getpid());
    FILE* writefd = popen(cmd,"w");
    fwrite(buf,sizeof(char),strlen(buf),writefd);
    fclose(writefd);
}

ps_self03.c:用于打印出自身和ps以及grep子进程的信息

#include 
#include 
#include 
#include 

int main(){
    int fd[2];
    pipe(fd);
    
    if(!fork()){
        dup2(fd[1],STDOUT_FILENO);
        execlp("ps","ps","-o","pid,ppid,stat,cmd",0);
    }else{
        if(!fork()){
            dup2(fd[0],STDIN_FILENO);
            char buf[128] = {0};
            sprintf(buf,"%d",getppid());
            execlp("grep","grep",buf,0);
        }else{
            wait(NULL);
            wait(NULL); 
        }
    }
    close(fd[0]);
    close(fd[1]);
}

ps_self04.c:更加规范的关闭管道的方式:

#include 
#include 
#include 
#include 
#include 

int main(){
    int fd[2];
    pipe(fd);
    
    if(!fork()){
        close(fd[0]);
        dup2(fd[1],STDOUT_FILENO);
        execlp("ps","ps","-o","pid,ppid,stat,cmd",0);
        close(fd[1]);
    }else{
        if(!fork()){
            close(fd[1]);
            dup2(fd[0],STDIN_FILENO);
            char buf[128] = {0};
            sprintf(buf,"%d",getppid());
            execlp("grep","grep",buf,0);
            close(fd[0]);
        }else{
            close(fd[0]);
            close(fd[1]);
            wait(NULL);
            wait(NULL); 
        }
    }
}

ps_pther.c:函数用于打印参数中命令相关的父子进程;

命名管道

创建命名管道:int mkfifo(pathname,mode);pathname:表示文件名称路径,文件必须不存在,否则就会出错;mode:表示模式;返回值:0表示成功;非0表示失败;
 打开FIFO文件:int open(const char *path,int mode),pathname表示文件路径;mode:O_REONLY:表示阻塞只读;O_RDONLY|O_NONBLOCK表示非阻塞只读;O_WRONLY:表示阻塞只写;O_WRONLY|O_NONBLOCK:表示非阻塞只写;如果返回-1表示失败,否则返回的是文件描述符;
 命名管道可以是非亲缘进程之间,读写必须同时执行,否则就会阻塞;
mkfifo.c:用于创建命名管道:

#include 
#include 
#include 
#include 

int main(){
    if(-1 == mkfifo("/tmp/test",0644)){
        perror("mkfifo error");
        return 1;
    }
}

fifocreate.c:将数据写入命名管道:

#include 
#include 
#include 
#include 

int main(int argc,char **argv){
    int c,mode=0644;
    while((c = getopt(argc,argv,"t:"))!=1){
        switch(c){
        case 't':
            mode = strtol(optarg,NULL,8);
            break;
        }   
    }
    
    if(optind != argc - 1){
        printf("usage:%s [-t ] \n",argv[0]);
        return 1;
    }
    if(-1 == mkfifo(argv[optind],mode)){
        perror("mkfifo error");
        return 1;
    }
}

fiforead.c:用于读取命名管道数据:

#include 
#include 
#include 
#include 
#include 

int main(){
    int fd = open("/tmp/test2",O_RDONLY);
    if(-1 == fd){
        perror("open error");
        return 1;
    }
    char buf[BUFSIZ];
    bzero(buf,BUFSIZ);
    fcntl(fd,F_SETFL,O_NONBLOCK);
    read(fd,buf,BUFSIZ);
    printf("read: %s\n",buf);
}

需要注意的是fifowrite和fiforead要同时执行,才会传递数据,否则会陷入阻塞;
不恰当的非阻塞方式:
fifowrote01.c:

#include 
#include 
#include 
#include 
#include 

int main(){
    int fd = open("/tmp/test",O_WRONLY);
    if(-1 == fd){
        perror("open error");
        return 1;
    }
    char str[] = "Hello fifo";
    write(fd,str,sizeof(str));
    printf("write:%s\n",str);
}

不恰当的读取命名管道的方式:fiforead01.c:

#include 
#include 
#include 
#include 
#include 

int main(){
    int fd = open("/tmp/test",O_RDONLY);
    if(-1 == fd){
        perror("open error");
        return 1;
    }
    char buf[BUFSIZ];
    bzero(buf,BUFSIZ);
    read(fd,buf,BUFSIZ);
    printf("read:%s\n",buf);
}

正确的非阻塞方式:
写入命名管道:
fifowrite02.c:

#include 
#include 
#include 
#include 
#include 

int main(){
    int fd = open("/tmp/test",O_WRONLY|O_NONBLOCK);
    if(-1 == fd){
        perror("open error");
        return 1;
    }
    char str[] = "Hello fifo";
    write(fd,str,sizeof(str));
    printf("write:%s\n",str);
}

正确的读取命名管道的方式:
fiforead02.c:

#include 
#include 
#include 
#include 
#include 

int main(){
    int fd = open("/tmp/test",O_RDONLY|O_NONBLOCK);
    if(-1 == fd){
        perror("open error");
        return 1;
    }
    char buf[BUFSIZ];
    bzero(buf,BUFSIZ);
    read(fd,buf,BUFSIZ);
    printf("read:%s\n",buf);
}

需要注意的是:fcntl(fd,F_SETFL,O_NONBLOCK)不能控制命名管道与读写阻塞问题,必须在open命名管道时,指定非阻塞打开;
删除命名管道:
fiform.c:

#include 

int main(){
    unlink("/tmp/test");
}

dup

dup01.c:用于复制标准输出,新文件描述符和就文件描述符不同,但是具备就文件描述符功能

#include 
#include 
#include 
#include 

int main(){
    int fd = dup(STDOUT_FILENO);
    fprintf(fdopen(fd,"w"),"%d printf:Hello dup\n",fd);
}

dup02.c:复制文件描述符,新文件描述符与旧文件描述符不同,但是具有就文件描述符功能;

#include 
#include 
#include 
#include 
#include 
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)

int main(){
    int fd = open("./test",O_CREAT|O_RDWR,FILE_MODE); 
    char str[]="Hello dup\n";
    write(fd,str,sizeof(str));
    int cp_fd = dup(fd);
    printf("copy %d to %d",fd,cp_fd);
    write(cp_fd,str,sizeof(str));
    //fprintf(fdopen(fd,"w"),"%d printf:Hello dup\n",fd);
    close(fd);
}

你可能感兴趣的:(Linux-C-day-2-进程通过--管道通信)