2.10~2.29 进程间通信(上)

简介: 2.10~2.29 进程间通信

一、进程通信简介


01 进程通信概念



02 Linux进程间通信的方式



二、管道


03 匿名管道



04 管道的特点




  • 管道通信数据是一次性的


  • 匿名管道只能用于有关系的进程


05 为什么可以使用管道进行进程间的通信


因为共享文件描述符



06 管道的数据结构


环形队列,逻辑上是



07 匿名管道的使用


  1. 创建匿名管道


#include<unistd.h>
int pipe(int pipefd[2]);


查看管道缓冲大小命令


ulimit -a


查看管道缓冲大小函数


#include<unistd.h>
long fpathconf(int fd, int name);


例:


/*
    #include <unistd.h>
    int pipe(int pipefd[2]);
        功能:创建一个匿名管道,用来进程间通信。
        参数:int pipefd[2] 这个数组是一个传出参数。
            pipefd[0] 对应的是管道的读端
            pipefd[1] 对应的是管道的写端
        返回值:
            成功 0
            失败 -1
    管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞
    注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程)
*/
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 子进程发送数据给父进程,父进程读取到数据输出
int main() {
    // 在fork之前创建管道
    int pipefd[2];           //0是读端,1是写端
    int ret = pipe(pipefd);  //创建管道,ret为对应的文件描述符
    if(ret == -1) {          //创建失败的操作
        perror("pipe");
        exit(0);
    }
    // 创建子进程
    pid_t pid = fork();
    if(pid > 0) {
        // 父进程从管道的读取端读取数据
        char buf[1024] = {0};
        int len = read(pipefd[0], buf, sizeof(buf));
        printf("parent recv : %s, pid : %d\n", buf, getpid());
    } 
    else if(pid == 0){// 子进程
        char buf[1024] = {0};
        // 子进程向管道中写入数据
        char * str = "hello,i am child";
        write(pipefd[1], str, strlen(str));       
    }
    return 0;
}



例:匿名管道实现双向读写数据


/*
    #include <unistd.h>
    int pipe(int pipefd[2]);
        功能:创建一个匿名管道,用来进程间通信。
        参数:int pipefd[2] 这个数组是一个传出参数。
            pipefd[0] 对应的是管道的读端
            pipefd[1] 对应的是管道的写端
        返回值:
            成功 0
            失败 -1
    管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞
    注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程)
*/
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
    // 在fork之前创建管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1) {
        perror("pipe");
        exit(0);
    }
    // 创建子进程
    pid_t pid = fork();
    if(pid > 0) {   // 父进程
        printf("i am parent process, pid : %d\n", getpid());
        // 从管道的读取端读取数据
        char buf[1024] = {0};
        while(1) {   //连续读取数据    没有读就阻塞  所以不需要sleep
            int len = read(pipefd[0], buf, sizeof(buf));
            printf("parent recv : %s, pid : %d\n", buf, getpid());
            // 向管道中写入数据
            char * str = "hello,i am parent";
            write(pipefd[1], str, strlen(str));
            sleep(1);
        }
    } 
    else if(pid == 0){  // 子进程
        printf("i am child process, pid : %d\n", getpid());
        char buf[1024] = {0};
        while(1) {    //连续发送数据
            // 向管道中写入数据
            char * str = "hello,i am child";
            write(pipefd[1], str, strlen(str));
            sleep(1);
      //读数据
            int len = read(pipefd[0], buf, sizeof(buf));
            printf("child recv : %s, pid : %d\n", buf, getpid());
        }
    }
    return 0;
}




例:通过fpathconf函数查看管道大小


#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
    int pipefd[2];
    int ret = pipe(pipefd);
    // 获取管道的大小
    long size = fpathconf(pipefd[0], _PC_PIPE_BUF);
    printf("pipe size : %ld\n", size);
    return 0;
}



例:匿名管道是半双工,所以只能一方读一方写


/*
    #include <unistd.h>
    int pipe(int pipefd[2]);
        功能:创建一个匿名管道,用来进程间通信。
        参数:int pipefd[2] 这个数组是一个传出参数。
            pipefd[0] 对应的是管道的读端
            pipefd[1] 对应的是管道的写端
        返回值:
            成功 0
            失败 -1
    管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞
    注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程)
*/
// 子进程发送数据给父进程,父进程读取到数据输出
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
    // 在fork之前创建管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1) {
        perror("pipe");
        exit(0);
    }
    // 创建子进程
    pid_t pid = fork();
    if(pid > 0) {  // 父进程读数据,关闭写端
        printf("i am parent process, pid : %d\n", getpid());
        // 关闭写端
        close(pipefd[1]);
        // 从管道的读取端读取数据
        char buf[1024] = {0};
        while(1) {
            int len = read(pipefd[0], buf, sizeof(buf));
            printf("parent recv : %s, pid : %d\n", buf, getpid());
            // 向管道中写入数据
            //char * str = "hello,i am parent";
            //write(pipefd[1], str, strlen(str));
            //sleep(1);
        }
    } 
    else if(pid == 0){  // 子进程写数据,关闭读端
        printf("i am child process, pid : %d\n", getpid());
        // 关闭读端
        close(pipefd[0]);
        char buf[1024] = {0};
        while(1) {
            // 向管道中写入数据
            char * str = "hello,i am child";
            write(pipefd[1], str, strlen(str));
            //sleep(1);
            // int len = read(pipefd[0], buf, sizeof(buf));
            // printf("child recv : %s, pid : %d\n", buf, getpid());
            // bzero(buf, 1024);
        }
    }
    return 0;
}


/*
    实现 ps aux | grep xxx 父子进程间通信
    子进程: ps aux, 子进程结束后,将数据发送给父进程
    父进程:获取到数据,过滤
    pipe()
    execlp()
    子进程将标准输出 stdout_fileno 重定向到管道的写端。  dup2
*/
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wait.h>
int main() {
    // 创建一个管道
    int fd[2];
    int ret = pipe(fd);
    if(ret == -1) {
        perror("pipe");
        exit(0);
    }
    // 创建子进程
    pid_t pid = fork();
    if(pid > 0) {
        // 父进程
        // 关闭写端
        close(fd[1]);
        // 从管道中读取
        char buf[1024] = {0};
        int len = -1;
        while((len = read(fd[0], buf, sizeof(buf) - 1)) > 0) { //判断buf是否有数据
            // 过滤数据输出
            printf("%s", buf);
            memset(buf, 0, 1024);  //清空buf
        }
        wait(NULL);
    } else if(pid == 0) {
        // 子进程
        // 关闭读端
        close(fd[0]);
        // 文件描述符的重定向 stdout_fileno -> fd[1]
        dup2(fd[1], STDOUT_FILENO);
        // 执行 ps aux
        execlp("ps", "ps", "aux", NULL);
        perror("execlp");
        exit(0);
    } else {
        perror("fork");
        exit(0);
    }
    return 0;
}


设置管道非阻塞:


#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>  //设置flag的
/*
    设置管道非阻塞
    int flags = fcntl(fd[0], F_GETFL);  // 获取原来的flag
    flags |= O_NONBLOCK;            // 修改flag的值
    fcntl(fd[0], F_SETFL, flags);   // 设置新的flag
*/
int main() {
    // 在fork之前创建管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1) {
        perror("pipe");
        exit(0);
    }
    // 创建子进程
    pid_t pid = fork();
    if(pid > 0) {
        // 父进程
        printf("i am parent process, pid : %d\n", getpid());
        // 关闭写端
        close(pipefd[1]);
        // 从管道的读取端读取数据
        char buf[1024] = {0};
        int flags = fcntl(pipefd[0], F_GETFL);  // 获取原来的flag
        flags |= O_NONBLOCK;            // 修改flag的值
        fcntl(pipefd[0], F_SETFL, flags);   // 设置新的flag   设置非阻塞
        while(1) {
            int len = read(pipefd[0], buf, sizeof(buf));
            printf("len : %d\n", len);
            printf("parent recv : %s, pid : %d\n", buf, getpid());
            memset(buf, 0, 1024);  //清空buf
            sleep(1);
        }
    } 
    else if(pid == 0){
        // 子进程
        printf("i am child process, pid : %d\n", getpid());
        // 关闭读端
        close(pipefd[0]);
        char buf[1024] = {0};
        while(1) {
            // 向管道中写入数据
            char * str = "hello,i am child";
            write(pipefd[1], str, strlen(str));
            sleep(5);
        }
    }
    return 0;
}



管道的读写特点:
使用管道时,需要注意以下几种特殊的情况(假设都是阻塞I/O操作)
1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端
读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。
2.如果有指向管道写端的文件描述符没有关闭(管道的写端引用计数大于0),而持有管道写端的进程
也没有往管道中写数据,这个时候有进程从管道中读取数据,那么管道中剩余的数据被读取后,
再次read会阻塞,直到管道中有数据可以读了才读取数据并返回。
3.如果所有指向管道读端的文件描述符都关闭了(管道的读端引用计数为0),这个时候有进程
向管道中写数据,那么该进程会收到一个信号SIGPIPE, 通常会导致进程异常终止。
4.如果有指向管道读端的文件描述符没有关闭(管道的读端引用计数大于0),而持有管道读端的进程
也没有从管道中读数据,这时有进程向管道中写数据,那么在管道被写满的时候再次write会阻塞,
直到管道中有空位置才能再次写入数据并返回。
总结:
    读管道:
        管道中有数据,read返回实际读到的字节数。
        管道中无数据:
            写端被全部关闭,read返回0(相当于读到文件的末尾)
            写端没有完全关闭,read阻塞等待
    写管道:
        管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)
        管道读端没有全部关闭:
            管道已满,write阻塞
            管道没有满,write将数据写入,并返回实际写入的字节数


08 有名管道




09 有名管道的使用



mkfifo.c


/*
    创建fifo文件
    1.通过命令: mkfifo 名字
    2.通过函数:int mkfifo(const char *pathname, mode_t mode);
    #include <sys/types.h>
    #include <sys/stat.h>
    int mkfifo(const char *pathname, mode_t mode);
        参数:
            - pathname: 管道名称的路径
            - mode: 文件的权限 和 open 的 mode 是一样的
                    是一个八进制的数
        返回值:成功返回0,失败返回-1,并设置错误号
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
    // 判断文件是否存在
    int ret = access("fifo1", F_OK);  //查看文件是否存在
    if(ret == -1) {
        printf("管道不存在,创建管道\n");
        ret = mkfifo("fifo1", 0664);  //第一个参数为路径  第二个参数为权限
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }       
    }
    return 0;
}


read.c


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
// 从管道中读取数据
int main() {
    // 1.打开管道文件
    int fd = open("test", O_RDONLY);
    if(fd == -1) {
        perror("open");
        exit(0);
    }
    // 读数据
    while(1) {
        char buf[1024] = {0};
        int len = read(fd, buf, sizeof(buf));
        if(len == 0) {   //-1是调用失败  0是读完了,结束循环
            printf("写端断开连接了...\n");
            break;
        }
        printf("recv buf : %s\n", buf);
    }
    close(fd);
    return 0;
}


write.c


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
// 向管道中写数据
/*
    有名管道的注意事项:
        1.一个为只读而打开一个管道的进程会阻塞,直到另外一个进程为只写打开管道
        2.一个为只写而打开一个管道的进程会阻塞,直到另外一个进程为只读打开管道
    读管道:
        管道中有数据,read返回实际读到的字节数
        管道中无数据:
            管道写端被全部关闭,read返回0,(相当于读到文件末尾)
            写端没有全部被关闭,read阻塞等待
    写管道:
        管道读端被全部关闭,进行异常终止(收到一个SIGPIPE信号)
        管道读端没有全部关闭:
            管道已经满了,write会阻塞
            管道没有满,write将数据写入,并返回实际写入的字节数。
*/
int main() {
    // 1.判断文件是否存在
    int ret = access("test", F_OK);
    if(ret == -1) {
        printf("管道不存在,创建管道\n");
        // 2.创建管道文件
        ret = mkfifo("test", 0664);
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }       
    }
    // 3.以只写的方式打开管道
    int fd = open("test", O_WRONLY);
    if(fd == -1) {
        perror("open");
        exit(0);
    }
    // 写数据
    for(int i = 0; i < 100; i++) {
        char buf[1024];
        sprintf(buf, "hello, %d\n", i);   //流中写入hello
        printf("write data : %s\n", buf);
        write(fd, buf, strlen(buf));
        sleep(1);
    }
    close(fd);
    return 0;
}


实现聊天



chatA.c,


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
int main() {
    // 1.判断有名管道文件是否存在
    int ret = access("fifo1", F_OK);
    if(ret == -1) {
        // 文件不存在
        printf("管道不存在,创建对应的有名管道\n");
        ret = mkfifo("fifo1", 0664);
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }
    }
    ret = access("fifo2", F_OK);
    if(ret == -1) {
        // 文件不存在
        printf("管道不存在,创建对应的有名管道\n");
        ret = mkfifo("fifo2", 0664);
        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }
    }
    // 2.以只写的方式打开管道fifo1
    int fdw = open("fifo1", O_WRONLY);
    if(fdw == -1) {
        perror("open");
        exit(0);
    }
    printf("打开管道fifo1成功,等待写入...\n");
    // 3.以只读的方式打开管道fifo2
    int fdr = open("fifo2", O_RDONLY);
    if(fdr == -1) {
        perror("open");
        exit(0);
    }
    printf("打开管道fifo2成功,等待读取...\n");
    char buf[128];
    // 4.循环的写读数据
    while(1) {
        memset(buf, 0, 128);
        // 获取标准输入的数据
        fgets(buf, 128, stdin);
        // 写数据
        ret = write(fdw, buf, strlen(buf));
        if(ret == -1) {
            perror("write");
            exit(0);
        }
        // 5.读管道数据
        memset(buf, 0, 128);
        ret = read(fdr, buf, 128);
        if(ret <= 0) {
            perror("read");
            break;
        }
        printf("buf: %s\n", buf);
    }
    // 6.关闭文件描述符
    close(fdr);
    close(fdw);
    return 0;
}


chatB.c类似

目录
相关文章
|
7月前
|
消息中间件 存储 Unix
进程间通信和线程间通信总结
写在前面 面试的时候一定不要疲劳战,比如上午面了一个,然后中午不休息直接赶到另外一个相距比较远的公司,影响状态。 面试的时候一定不要紧张,不管对方有几个人,总之面试的时候做好充分准备,休息好,放松心态。 好了,言归正传,开始总结。
67 0
|
存储 并行计算 算法
C++进程间通信之共享内存
C++进程间通信之共享内存
792 0
|
5月前
|
消息中间件 负载均衡 安全
进程间通信
数据传输:一个进程需要将它的数据发送给另一个进程资源共享:多个进程之间共享同样的资源。通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。进程间通信的本质:让不同的进程先看到同一份资源,但是这份资源不能由双方进程提供,而通常是由操作系统所提供!
39 9
进程间通信
|
存储 消息中间件 Linux
IPC(进程间通信)(上)
IPC(进程间通信)
76 0
|
API
IPC(进程间通信)(下)
IPC(进程间通信)(下)
57 0
|
Unix
进程间通信(一)
进程间通信
71 0
|
消息中间件
进程间通信(非常实用)
进程间通信(非常实用)
|
Linux
2.10~2.29 进程间通信(下)
2.10~2.29 进程间通信
109 0
2.10~2.29 进程间通信(下)
|
开发者
进程间通信 | 学习笔记
快速学习进程间通信,介绍了进程间通信系统机制, 以及在实际应用过程中如何使用。