2022-1-16牛客C++项目——Linux多进程编程——进程间通信

常见的面试题:
1、你知道进程间通信的方式有哪几种吗?
2、这个通信方式具体的实现原理是什么?怎么实现的?

复习时候需要使用的问题:
1、进程间的通信是什么?进程间为什么需要通信?
2、进程间通信的目的是什么?(有四个理由)

一、进程间为什么需要通信?
进程是一个独立的分配资源的单位,这意味着,进程与进程之间是无法访问对方的资源的。但是进程之间的协作需要知道其他进程之间的状态信息,这需要通过通信来交换数据。(共享一个数据块里头的信息)

二、进程间通信的目的是什么?
1、数据传输
2、事件通知(比如一个进程终止的时候需要发送消息给父进程)
3、资源共享(进程之间共享同一块数据资源,需要内核提供互斥和同步的机制)
4、进程控制(有些进程需要完全控制另外一个进程的运行,比如 用 gcc 来 debug。此时这个进程需要知道另外一个进程的运行状态,异常和终端的情况)

三、进程之间的通信方式有哪些?
在这里插入图片描述
匿名管道的使用例子:
在这里插入图片描述
四、管道的特点

管道其实是在内核区域中能够存储数据的一块缓冲区。

管道的文件特质体现在哪里?
管道两端是文件操作符:输入和输出的文件操作符。

匿名管道多用于两个有关系的进程之间的通信,如父子进程。
有名管道多用于没有关系的进程之间的通信。

如何理解字节流:
流里面的数据是没有格式的,也没有所谓开头和结尾的分隔符,读取的时候可以按照基本的数据类型的格式读取。也可以按照任意的大小读取。
在这里插入图片描述
在这里插入图片描述
五、为什么匿名管道可以用于有关系的进程之间的通信?
父进程在 fork 子进程的时候会拷贝一份文件描述符,因此父子进程的文件描述符指向同样的文件。而匿名管道就是通过文件描述符来读取和写入数据的。拥有同样的文件描述符说明可以对同一个匿名管道进行操作。

在这里插入图片描述
六、管道的数据结构
管道的数据结构是一个环形的队列,是半双工的。
在这里插入图片描述

/*
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个匿名管道,用于进程间的通信
参数:传出参数。(那不得malloc)
    pipefd[0] 读端的文件描述符
    pipefd[1] 写端的文件描述符
返回值:
-成功:0
-失败:-1,并且设置 perror
注意:匿名管道只能用于具有关系的进程间的通信,比如说(父子进程,兄弟进程,孙子进程)

*/
//子进程发送数据给父进程,父进程读取到数据之后读出
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
    //管道创建,需要在 fork 之前还是在 fork 之后?
    //需要在 fork 之前创建,感觉在 fork 之后创建会导致创建了两个管道
    int pipefd[2];
    int ret = pipe(pipefd);
    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 *str = "hello, i am child.";
        int len = write(pipefd[1],str,strlen(str));
//sizeof运算符计算出来的是字节,计算最后的'\0'
//strlen 函数计算出来的是字符串的长度,不包括最后面的'\0'
    }
}

在这里插入图片描述

管道默认是阻塞的,如果管道中没有数据,则 read 阻塞,如果管道已经写满了数据,则 write 阻塞。

/*
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个匿名管道,用于进程间的通信
参数:传出参数。(那不得malloc,不用)
    pipefd[0] 读端的文件描述符
    pipefd[1] 写端的文件描述符
返回值:
-成功:0
-失败:-1,并且设置 perror
注意:匿名管道只能用于具有关系的进程间的通信,比如说(父子进程,兄弟进程,孙子进程)

*/
//子进程发送数据给父进程,父进程读取到数据之后读出
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
    //管道创建,需要在 fork 之前还是在 fork 之后?
    //需要在 fork 之前创建,感觉在 fork 之后创建会导致创建了两个管道
    int pipefd[2];
    int ret = pipe(pipefd);
    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){
        sleep(10);//通过延后写入数据来达到管道阻塞的效果
        //延后写入数据会造成父进程在读数据的时候无数据可以读,父进程阻塞,终端显示程序输出滞后
        char *str = "hello, i am child.";
        int len = write(pipefd[1],str,strlen(str));
//sizeof运算符计算出来的是字节,计算最后的'\0'
//strlen 函数计算出来的是字符串的长度,不包括最后面的'\0'
    }
}

观察运行效果的时候,肉眼可见的阻塞了好久。
在这里插入图片描述

子进程持续向父进程发送数据,
父子程序同时写入和读出数据,会导致读到自己写入的数据。

/*
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个匿名管道,用于进程间的通信
参数:传出参数。(那不得malloc)
    pipefd[0] 读端的文件描述符
    pipefd[1] 写端的文件描述符
返回值:
-成功:0
-失败:-1,并且设置 perror
注意:匿名管道只能用于具有关系的进程间的通信,比如说(父子进程,兄弟进程,孙子进程)

*/
//子进程发送数据给父进程,父进程读取到数据之后读出
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
    //管道创建,需要在 fork 之前还是在 fork 之后?
    //需要在 fork 之前创建,感觉在 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());
        while (1)
        {
            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){
        printf("i am child process, pid : %d .\n",getpid());
      //  sleep(10);//通过延后写入数据来达到管道阻塞的效果
      while (1)
      {
        char *str = "hello, i am child.";
        int len = write(pipefd[1],str,strlen(str));
        sleep(1);
      }
      
        
//sizeof运算符计算出来的是字节,计算最后的'\0'
//strlen 函数计算出来的是字符串的长度,不包括最后面的'\0'
    }
}

父子进程能够通过管道向对方发送数据。
但是不可以同时先读,同时先读管道里面没有数据会造成阻塞。
如果同时先写会怎么样?
同时先写的运行结果,会得到自己写入的数据。
在这里插入图片描述

在写入数据之后都使用 sleep(1)来达到进程切换的目的,保证自己能够读到对方写入的数据。

#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
    //管道创建,需要在 fork 之前还是在 fork 之后?
    //需要在 fork 之前创建,感觉在 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());
        while (1)
        {
            char buf[1024] = {0};
            int len1 = read(pipefd[0],buf,sizeof(buf));
            printf("parent recv : %s ,pid : %d.\n",buf,getpid());

            char *str = "hello, i am parent.";
            int len2= write(pipefd[1],str,strlen(str));
            sleep(1);
        }
    }
    else if(pid == 0){
        printf("i am child process, pid : %d .\n",getpid());
      //  sleep(10);//通过延后写入数据来达到管道阻塞的效果
      while (1)
      {
        char *str = "hello, i am child.";
        int len1 = write(pipefd[1],str,strlen(str));
        sleep(1);

        char buf[1024] = {0};
        int len2 = read(pipefd[0],buf,sizeof(buf));
        printf("child recv : %s ,pid : %d.\n",buf,getpid());

      }    
//sizeof运算符计算出来的是字节,计算最后的'\0'
//strlen 函数计算出来的是字符串的长度,不包括最后面的'\0'
    }
}

在这里插入图片描述
查看管道的大小

#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
    int pipefd[2];
    int ret = pipe(pipefd);

    if(ret == -1){
        perror("pipe");
        exit(0);
    }
    //fpathconf()获取文件相关的配置信息
    long size = fpathconf(pipefd[0],_PC_PIPE_BUF);

    printf("pipe size : %ld \n",size);

}

在这里插入图片描述
使用 ulimits -a命令来查看
在这里插入图片描述
两者的结果是一致的。

三、匿名管道通信案例
如果子进程和父进程同时写,或者是写完之后读取的速度太块,会读到自己写的内容,这个是为什么?

正常的数据流向:
在这里插入图片描述
读到自己的内容的数据的流向:
在这里插入图片描述
个人觉得应该采用两个管道。。。。。

一般采用匿名管道都是一个进程读取一个进程写,如果两个进程都读都写会混乱。

上面的第一个示例程序当中会让进程休眠,交出 CPU时间片,所以不会出现这样的幺蛾子。

在这里插入图片描述
可以通过关闭文件操作符来关闭进程对管道读端和写端的控制。

#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){

    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]);
        while (1)
        {
            char buf[1024] = {0};
            int len1 = read(pipefd[0],buf,sizeof(buf));
            printf("parent recv : %s ,pid : %d.\n",buf,getpid());
        }
    }
    else if(pid == 0){
        printf("i am child process, pid : %d .\n",getpid());
      //子进程如果不读数据可以将读端关闭了
        close(pipefd[0]);
      while (1)
      {

        char *str = "hello, i am child.";
        int len1 = write(pipefd[1],str,strlen(str));
      }    
    }
}

四、使用管道进行通信的案例

/*
实现 ps aux | grep XXX 父子进程间的通信
子进程 : ps aux,子进程结束后, 将数据发送给父进程
父进程:获取到数据,过滤

pipe()
execlp()
子进程将指向终端的标准输出重定向到管道的写端
*/
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
    //创建管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1){
        perror("pipe");
        exit(0);
    }
   // return 0;

    //创建子进程
    pid_t pid = fork();
    if(pid > 0){
        //父进程
        //关闭写段
        close(pipefd[1]);
        //从管道中读取,但是这种写法只能读取一次
        char buf[1024];
        // int len = read(pipefd[0],buf,sizeof(buf)-1);
        // printf("%s",buf);
        //循环读取
        int len = -1;
        while ((len = read(pipefd[0],buf,sizeof(buf)-1)) > 0){
            printf("%s",buf);
        }
        
        

        //
    }
    else if(pid == 0){
        //子进程
        //关闭读端
        close(pipefd[0]);
        //文件描述符的重定向 STDOUT_FILENO->fd[1]
        //否则会在当前的终端中输出
        dup2(pipefd[1],STDOUT_FILENO);
        //执行 ps aux
        execlp("ps","ps","aux",NULL);
        perror("execlp");
        exit(0);
    }
    else{
        perror("fork");
        exit(0);
    }
    return 0;
    
}

输出的内容有限,因为管道的缓冲区的大小有限,写满了之后就无法继续写入。

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值