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 <stdio.h>
#include <stdlib.h>
#include <string.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>

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 <stdio.h>
#include <stdlib.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

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

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

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 <mode>] <pathname>\n",argv[0]);
        return 1;
    }
    if(-1 == mkfifo(argv[optind],mode)){
        perror("mkfifo error");
        return 1;
    }
}

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

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 <unistd.h>

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

dup

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

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

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#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);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容