Linux篇14进程间通信之管道

1.为什么要进程间通信?

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

2.进程间通信是如何做到的?

进程运行时具有独立性,进程间通信,一定要借助第三方(OS)资源。OS一定要提供一段内存区域,能够被双方进程看到。通信的本质就是“数据的拷贝”。

进程A->数据“拷贝”给OS ->OS->数据“拷贝”给进程B

3.管道

我们把从一个进程连接到另一个进程的一个数据流称为一个管道。所谓的管道,就是内核里面的一串缓存。从管道的一端写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。

3.1匿名管道

image-20220710142039291

image-20220718201411949

管道只能 进行单方向通信

上面所说的管道没有名字,称为匿名管道。想要创建匿名管道,需要使用函数pipe();

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>                                           
#include <unistd.h>                                              
#include <string.h>
int main()
{
  int fd[2] = {0};//用来保存pipe返回的两个文件描述符 
  if(pipe(fd) < 0)
  {
    //管道创建失败
    perror("pipe!");
    return 1;
  }

 // printf("fd[0]: %d\n", fd[0]);
 // printf("fd[1]: %d\n", fd[1]);
  pid_t id = fork();
  //以子进程写,父进程读为例
  if(id == 0)
  {
    //child
    const char* msg = "hello father, I am child!";
    close(fd[0]);//子进程关闭读端,保留写端
    int count = 30;
    while(count--)
    {
       sleep(1);
       write(fd[1], msg, strlen(msg));
    }
    close(fd[1]);
    exit(0);
  }

  //father
  close(fd[1]);//父进程关闭写端,保留读端
  char buff[64];
  while(1)
  {
    ssize_t s = read(fd[0], buff, sizeof(buff));//ssize_t是有符号整数,size_t是无符号整数
    if(s > 0)
    {
      buff[s] = '\0';
      printf("child send to father# %s\n", buff);
    }
    else if (s == 0)
    {
      printf("read file\n");
      break;
    }
    else 
    {
      perror("read\n");
      break;
    }
  }
  int status = 0;
  waitpid(id, &status, 0);
  printf("child quit!: sig: %d\n", status & 0x7F);  
  return 0;
}

父子进程通信时,能不能创建全局缓冲区来完成通信呢?

  • 绝对不可以的,进程运行具有独立性。任何一方对数据的修改另一方是不可以看到的。

管道的特点

  • 管道内部已经自动提供了互斥与同步机制,就不会出现写到一半就开始读

    read->管道里面没有数据或write->管道里面没有空间,对应进程会被挂起

  • 如果写端关闭了,读端就会read到返回值0代表文件结束

  • 如果打开文件的进程退出了,所打开的文件也就会被释放掉。文件的生命周期随进程

  • 管道是提供流式服务

  • 管道是半双工通信

    全双工(人在吵架的时候,你说我也说) 半双工(人在正常沟通的时候,一个人说,一个人听)

  • 匿名管道,适合具有血缘关系的进程进行进程间通信,常用于父子

不write,一直read,read阻塞。

不read,一直write,write阻塞

write写完,关闭写端,read返回值0

read端关闭,一直写,写方(child)被OS杀掉,因为写入无意义。子进程属于异常退出,收到了信号。

image-20220718211445431

image-20220718211234266

image-20220718211323987

管道是一段内存缓冲区,肯定是有大小的,下面我么来试试管道有多大

image-20220718212314998

image-20220718212334462

image-20220718212350967

经过测试,我当前系统的管道大小为65536字节

image-20220718212535350

3.2命名管道

上面的匿名管道通信,适用于具有血缘关系的进程间通信。那么两个互不相干的进程怎么实现进程间通信呢?

这就用到了命名管道。命名管道有名字,那么不同进程就可以通过名字打开同一个文件了,也就看到了同一份资源。

创建命名管道方式1命令行创建

image-20220718213310055

image-20220718213735713

创建命名管道方式2程序里面创建

  1. 测试代码1

    common.h

    #pragma once
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    #define FILE_NAME "myfifo"
    

    server.h

    #include "common.h"
    int main()
    {
      if(mkfifo(FILE_NAME, 0644) < 0)
      {
        perror("mkfifo");
        return 1;
      }
      int fd = open(FILE_NAME, O_RDONLY);
      if(fd < 0)
      {
        //open失败
        perror("open error!\n");
        return 2;
      }
    
      char msg[128];
      while(1)
      {
        msg[0] = 0;//先清空msg里面的内容
        ssize_t s = read(fd, msg, sizeof(msg)-1);
        if(s > 0)
        {//读成功了
          msg[s] = 0;
          printf("client# %s\n", msg);
        }
        else if(s == 0)
        {//读完了
          printf("client quit!\n");
          break;
        }
        else 
        {
          //读失败了
          perror("read error\n");
          break;
        }
      }
      
      close(fd);
    
      return 0;
    }
    
    

    client.c

    #include "common.h"
    
    int main()
    {
      int fd = open(FILE_NAME, O_WRONLY);
      if(fd < 0)
      {
        perror("open error\n");
        return 1;
      }
      char msg[128];
      while(1)
      {
       msg[0] = 0;
       printf("Please Enter# ");
       fflush(stdout);
       ssize_t s = read(0, msg, sizeof(msg)-1);
       if(s > 0)
       {
         msg[s] = 0;
         write(fd, msg, strlen(msg));
       }
       
      }
      close(fd);
      return 0;
    }
    

    image-20220723215400679

  2. 测试代码2

    一个进程控制另一个进程完成任务

    server.c

    #include "common.h"
    #include <stdlib.h>
    #include <sys/wait.h>
    int main()
    {
      if(mkfifo(FILE_NAME, 0644) < 0)
      {
        perror("mkfifo");
        return 1;
      }
      int fd = open(FILE_NAME, O_RDONLY);
      if(fd < 0)
      {
        //open失败
        perror("open error!\n");
        return 2;
      }
    
      char msg[128];
      while(1)
      {
        msg[0] = 0;//先清空msg里面的内容
        ssize_t s = read(fd, msg, sizeof(msg)-1);
        if(s > 0)
        {//读成功了
          msg[s-1] = 0;//防止空行,相当于把\n改成'\0'
          printf("client# %s\n", msg);
          if(fork() == 0)
          {
            //创建一个子进程完成一个功能
            execlp(msg, msg, NULL);
            exit(1);
          }
          waitpid(-1, NULL, 0);
        }
        else if(s == 0)
        {//读完了
          printf("client quit!\n");
          break;
        }
        else 
        {
          //读失败了
          perror("read error\n");
          break;
        }
      }
      
      close(fd);
    
      return 0;
    }
    
    

    其余代码与测试代码1相同

    image-20220724000836140

  3. 测试代码3

    一个进程让另一个进程做简单的数字计算

    server.c

    #include "common.h"
    #include <stdlib.h>
    #include <sys/wait.h>
    int main()
    {
      if(mkfifo(FILE_NAME, 0644) < 0)
      {
        perror("mkfifo");
        return 1;
      }
      int fd = open(FILE_NAME, O_RDONLY);
      if(fd < 0)
      {
        //open失败
        perror("open error!\n");
        return 2;
      }
    
      char msg[128];
      while(1)
      {
        msg[0] = 0;//先清空msg里面的内容
        ssize_t s = read(fd, msg, sizeof(msg)-1);
        if(s > 0)
        {//读成功了
          msg[s-1] = 0;//防止空行,相当于把\n改成'\0'
          printf("client# %s\n", msg);//3 + 5
          char* p = msg;
          char* lable = "+-*/%";//保存分隔符
          int flag = 0;
          while(*p)
          {
            switch(*p)
            {
              case '+':
                flag = 0;
                break;
              case  '-':
                flag = 1;
                break;
              case '*':
                flag = 2;
                break;
              case '/':
                flag = 3;
                break;
              case '%':
                flag = 4;
                break;
            }
            p++;
          }
          char* data1 = strtok(msg, "+-*/%");
          char* data2 = strtok(NULL, "+-*/%");
    
          //转成整数
          int x = atoi(data1);
          int y = atoi(data2);
    
          int z = 0;
          switch(flag)
          {
            case 0:
              z = x + y;
              break;
            case 1:
              z = x - y;
              break;
            case 2:
              z = x * y;
              break;
            case 3:
              z = x / y;
              break;
            case 4:
              z = x % y;
              break;
          }
          printf("%d %c %d = %d\n", x, lable[flag], y, z);
         
        }
        else if(s == 0)
        {//读完了
          printf("client quit!\n");
          break;
        }
        else 
        {
          //读失败了
          perror("read error\n");
          break;
        }
      }
      
      close(fd);
    
      return 0;
    }
    

    其余代码同测试代码1

    image-20220724005520073

  4. 测试代码4

    本地有一个file.txt的文件,client将file.txt的内容放进管道。server将管道中的数据读取出来,在本地创建一个file-bak.txt,把读取的内容写到file-bat.txt

    server.c

    #include "common.h"
    #include <stdlib.h>
    #include <sys/wait.h>
    int main()
    {
      if(mkfifo(FILE_NAME, 0644) < 0)
      {
        perror("mkfifo");
        return 1;
      }
      int fd = open(FILE_NAME, O_RDONLY);
      if(fd < 0)
      {
        //open失败
        perror("open error!\n");
        return 2;
      }
    
      int out = open("file-bak.txt", O_CREAT|O_WRONLY, 0644);
      char msg[128];
      while(1)
      {
        msg[0] = 0;//先清空msg里面的内容
        ssize_t s = read(fd, msg, sizeof(msg)-1);
        if(s > 0)
        {//读成功了
          write(out, msg, s);
        }
        else if(s == 0)
        {//读完了
          printf("client quit!\n");
          break;
        }
        else 
        {
          //读失败了
          perror("read error\n");
          break;
        }
      }
      
      close(fd);
      close(out);
      return 0;
    }
    

    client.c

    #include "common.h"
    
    int main()
    {
    
        int fd = open(FILE_NAME, O_WRONLY);
        if(fd < 0)
        {
          perror("open error\n");
          return 1;
        }
    
        int in = open("file.txt", O_RDONLY);
        char msg[128];
        while(1)
        {
          msg[0] = 0;
          ssize_t s = read(in, msg, sizeof(msg));
          if(s == sizeof(msg))
          {
            //没读完呢,msg存不下file.txt里面的全部内容,先把目前读到的写进管道然后继续读
            write(fd, msg, s);
          }
          else if(s < sizeof(msg))
          {
            //读完了
            write(fd, msg, s);
            printf("read end of file\n");
            break;
          }
          else 
          {
            break;
          }
          
        }
    
        close(in);
        close(fd);
        return 0;
    }
    

    common.h同测试代码1

image-20220724013219822

4.匿名管道与命名管道的区别

匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开用open
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逃跑的机械工

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值